/***************************************************************************
 *   Copyright (c) 2009 Juergen Riegel <juergen.riegel@web.de>             *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"
#ifndef _PreComp_
#include <Inventor/SbBox3f.h>
#include <Inventor/SbLine.h>
#include <Inventor/SbTime.h>
#include <Inventor/SoPickedPoint.h>
#include <Inventor/actions/SoGetBoundingBoxAction.h>
#include <Inventor/details/SoPointDetail.h>
#include <Inventor/events/SoKeyboardEvent.h>
#include <Inventor/nodes/SoCamera.h>

#include <QApplication>
#include <QFontMetricsF>
#include <QMenu>
#include <QMessageBox>
#include <QScreen>
#include <QTextStream>
#endif

#include <Base/Console.h>
#include <Base/Vector3D.h>
#include <Gui/Application.h>
#include <Gui/BitmapFactory.h>
#include <Gui/CommandT.h>
#include <Gui/Control.h>
#include <Gui/Document.h>
#include <Gui/MainWindow.h>
#include <Gui/MenuManager.h>
#include <Gui/Selection.h>
#include <Gui/SelectionObject.h>
#include <Gui/SoFCUnifiedSelection.h>
#include <Gui/Utilities.h>
#include <Gui/View3DInventor.h>
#include <Gui/View3DInventorViewer.h>
#include <Mod/Part/App/Geometry.h>
#include <Mod/Sketcher/App/GeoList.h>
#include <Mod/Sketcher/App/GeometryFacade.h>
#include <Mod/Sketcher/App/SketchObject.h>
#include <Mod/Sketcher/App/SolverGeometryExtension.h>

#include "DrawSketchHandler.h"
#include "EditDatumDialog.h"
#include "EditModeCoinManager.h"
#include "SnapManager.h"
#include "TaskDlgEditSketch.h"
#include "TaskSketcherValidation.h"
#include "Utils.h"
#include "ViewProviderSketch.h"
#include "ViewProviderSketchGeometryExtension.h"
#include "Workbench.h"


// clang-format off
FC_LOG_LEVEL_INIT("Sketch", true, true)

using namespace SketcherGui;
using namespace Sketcher;
namespace sp = std::placeholders;

/************** ViewProviderSketch::ParameterObserver *********************/

template<typename T>
T getSketcherGeneralParameter(const std::string& string, T defaultvalue)
{
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
        "User parameter:BaseApp/Preferences/Mod/Sketcher/General");

    if constexpr (std::is_same_v<decltype(defaultvalue), unsigned int>) {
        return static_cast<unsigned int>(hGrp->GetUnsigned(string.c_str(), defaultvalue));
    }
    else if constexpr (std::is_same_v<decltype(defaultvalue), int>) {
        return static_cast<int>(hGrp->GetInt(string.c_str(), defaultvalue));
    }
}

template<typename T>
T getPreferencesViewParameter(const std::string& string, T defaultvalue)
{
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
        "User parameter:BaseApp/Preferences/View");

    if constexpr (std::is_same_v<decltype(defaultvalue), unsigned int>) {
        return static_cast<unsigned int>(hGrp->GetUnsigned(string.c_str(), defaultvalue));
    }
    else if constexpr (std::is_same_v<decltype(defaultvalue), int>) {
        return static_cast<int>(hGrp->GetInt(string.c_str(), defaultvalue));
    }
}

ViewProviderSketch::ParameterObserver::ParameterObserver(ViewProviderSketch& client)
    : Client(client)
{}

ViewProviderSketch::ParameterObserver::~ParameterObserver()
{
    unsubscribeToParameters();
}

void ViewProviderSketch::ParameterObserver::updateBoolProperty(const std::string& string,
                                                               App::Property* property,
                                                               bool defaultvalue)
{
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
        "User parameter:BaseApp/Preferences/Mod/Sketcher/General");

    auto boolprop = static_cast<App::PropertyBool*>(property);
    boolprop->setValue(hGrp->GetBool(string.c_str(), defaultvalue));
}

void ViewProviderSketch::ParameterObserver::updateColorProperty(const std::string& string,
                                                                App::Property* property, float r,
                                                                float g, float b)
{
    ParameterGrp::handle hGrp =
        App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");

    auto colorprop = static_cast<App::PropertyColor*>(property);

    colorprop->setValue(r, g, b);

    App::Color elementAppColor = colorprop->getValue();
    unsigned long color = (unsigned long)(elementAppColor.getPackedValue());
    color = hGrp->GetUnsigned(string.c_str(), color);
    elementAppColor.setPackedValue((uint32_t)color);
    colorprop->setValue(elementAppColor);
}

void ViewProviderSketch::ParameterObserver::updateGridSize(const std::string& string,
                                                           App::Property* property)
{
    (void)property;
    (void)string;

    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
        "User parameter:BaseApp/Preferences/Mod/Sketcher/General");

    Client.GridSize.setValue(
        Base::Quantity::parse(
            QString::fromLatin1(hGrp->GetGroup("GridSize")->GetASCII("GridSize", "10.0").c_str()))
            .getValue());
}

void ViewProviderSketch::ParameterObserver::updateEscapeKeyBehaviour(const std::string& string,
                                                                     App::Property* property)
{
    (void)property;
    (void)string;

    ParameterGrp::handle hSketch = App::GetApplication().GetParameterGroupByPath(
        "User parameter:BaseApp/Preferences/Mod/Sketcher");
    Client.viewProviderParameters.handleEscapeButton =
        !hSketch->GetBool("LeaveSketchWithEscape", true);
}

void ViewProviderSketch::ParameterObserver::updateAutoRecompute(const std::string& string,
                                                                App::Property* property)
{
    (void)property;
    (void)string;

    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
        "User parameter:BaseApp/Preferences/Mod/Sketcher");
    Client.viewProviderParameters.autoRecompute = hGrp->GetBool("AutoRecompute", false);
}

void ViewProviderSketch::ParameterObserver::updateRecalculateInitialSolutionWhileDragging(
    const std::string& string, App::Property* property)
{
    (void)property;
    (void)string;

    ParameterGrp::handle hGrp2 = App::GetApplication().GetParameterGroupByPath(
        "User parameter:BaseApp/Preferences/Mod/Sketcher");

    Client.viewProviderParameters.recalculateInitialSolutionWhileDragging =
        hGrp2->GetBool("RecalculateInitialSolutionWhileDragging", true);
}

void ViewProviderSketch::ParameterObserver::subscribeToParameters()
{
    try {
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
            "User parameter:BaseApp/Preferences/Mod/Sketcher/General");
        hGrp->Attach(this);

        ParameterGrp::handle hGrp2 = App::GetApplication().GetParameterGroupByPath(
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
        hGrp2->Attach(this);

        ParameterGrp::handle hGrpv = App::GetApplication().GetParameterGroupByPath(
            "User parameter:BaseApp/Preferences/View");
        hGrpv->Attach(this);
    }
    catch (const Base::ValueError& e) {// ensure that if parameter strings are not well-formed, the
                                       // exception is not propagated
        Base::Console().DeveloperError(
            "ViewProviderSketch", "Malformed parameter string: %s\n", e.what());
    }
}

void ViewProviderSketch::ParameterObserver::unsubscribeToParameters()
{
    try {
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
            "User parameter:BaseApp/Preferences/Mod/Sketcher/General");
        hGrp->Detach(this);

        ParameterGrp::handle hGrp2 = App::GetApplication().GetParameterGroupByPath(
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
        hGrp2->Detach(this);

        ParameterGrp::handle hGrpv = App::GetApplication().GetParameterGroupByPath(
            "User parameter:BaseApp/Preferences/View");
        hGrpv->Detach(this);
    }
    catch (const Base::ValueError& e) {// ensure that if parameter strings are not well-formed, the
                                       // exception is not propagated
        Base::Console().DeveloperError(
            "ViewProviderSketch", "Malformed parameter string: %s\n", e.what());
    }
}

void ViewProviderSketch::ParameterObserver::initParameters()
{
    // once initialize the map with the properties

    SbColor defaultGridColor(0.7f, 0.7f, 0.7f);
    unsigned int packedDefaultGridColor = defaultGridColor.getPackedValue();

    parameterMap = {
        {"HideDependent",
         {[this](const std::string& string, App::Property* property) {
              updateBoolProperty(string, property, true);
          },
          &Client.HideDependent}},
        {"ShowLinks",
         {[this](const std::string& string, App::Property* property) {
              updateBoolProperty(string, property, true);
          },
          &Client.ShowLinks}},
        {"ShowSupport",
         {[this](const std::string& string, App::Property* property) {
              updateBoolProperty(string, property, true);
          },
          &Client.ShowSupport}},
        {"RestoreCamera",
         {[this](const std::string& string, App::Property* property) {
              updateBoolProperty(string, property, true);
          },
          &Client.RestoreCamera}},
        {"ForceOrtho",
         {[this](const std::string& string, App::Property* property) {
              updateBoolProperty(string, property, false);
          },
          &Client.ForceOrtho}},
        {"SectionView",
         {[this](const std::string& string, App::Property* property) {
              updateBoolProperty(string, property, false);
          },
          &Client.SectionView}},
        {"AutoConstraints",
         {[this](const std::string& string, App::Property* property) {
              updateBoolProperty(string, property, true);
          },
          &Client.Autoconstraints}},
        {"AvoidRedundantAutoconstraints",
         {[this](const std::string& string, App::Property* property) {
              updateBoolProperty(string, property, true);
          },
          &Client.AvoidRedundant}},
        {"updateEscapeKeyBehaviour",
         {[this](const std::string& string, App::Property* property) {
              updateEscapeKeyBehaviour(string, property);
          },
          nullptr}},
        {"AutoRecompute",
         {[this](const std::string& string, App::Property* property) {
              updateAutoRecompute(string, property);
          },
          nullptr}},
        {"RecalculateInitialSolutionWhileDragging",
         {[this](const std::string& string, App::Property* property) {
              updateRecalculateInitialSolutionWhileDragging(string, property);
          },
          nullptr}},
        {"GridSizePixelThreshold",
         {[this](const std::string& string, [[maybe_unused]] App::Property* property) {
              auto v = getSketcherGeneralParameter(string, 15);
              Client.setGridSizePixelThreshold(v);
          },
          nullptr}},
        {"GridNumberSubdivision",
         {[this](const std::string& string, [[maybe_unused]] App::Property* property) {
              auto v = getSketcherGeneralParameter(string, 10);
              Client.setGridNumberSubdivision(v);
          },
          nullptr}},
        {"GridLinePattern",
         {[this](const std::string& string, [[maybe_unused]] App::Property* property) {
              auto v = getSketcherGeneralParameter(string, 0x0f0f);
              Client.setGridLinePattern(v);
          },
          nullptr}},
        {"GridDivLinePattern",
         {[this](const std::string& string, [[maybe_unused]] App::Property* property) {
              auto v = getSketcherGeneralParameter(string, 0xffff);
              Client.setGridDivLinePattern(v);
          },
          nullptr}},
        {"GridLineWidth",
         {[this](const std::string& string, [[maybe_unused]] App::Property* property) {
              auto v = getSketcherGeneralParameter(string, 1);
              Client.setGridLineWidth(v);
          },
          nullptr}},
        {"GridDivLineWidth",
         {[this](const std::string& string, [[maybe_unused]] App::Property* property) {
              auto v = getSketcherGeneralParameter(string, 2);
              Client.setGridDivLineWidth(v);
          },
          nullptr}},
        {"GridLineColor",
         {[this, packedDefaultGridColor](const std::string& string,
                                         [[maybe_unused]] App::Property* property) {
              auto v = getSketcherGeneralParameter(string, packedDefaultGridColor);
              auto color = App::Color(v);
              Client.setGridLineColor(color);
          },
          nullptr}},
        {"GridDivLineColor",
         {[this, packedDefaultGridColor](const std::string& string,
                                         [[maybe_unused]] App::Property* property) {
              auto v = getSketcherGeneralParameter(string, packedDefaultGridColor);
              auto color = App::Color(v);
              Client.setGridDivLineColor(color);
          },
          nullptr}},
        {"SegmentsPerGeometry",
         {[this](const std::string& string,
                                         [[maybe_unused]] App::Property* property) {
              auto v = getPreferencesViewParameter(string, 50); //LINT
              Client.viewProviderParameters.stdCountSegments = v;
          },
          nullptr}},
    };

    for (auto& val : parameterMap) {
        auto string = val.first;
        auto update = std::get<0>(val.second);
        auto property = std::get<1>(val.second);

        update(string, property);
    }

    // unsubscribed parameters which update a property on just once upon construction (and before
    // restore if properties are being restored from a file)
    updateColorProperty("SketchEdgeColor", &Client.LineColor, 1.0f, 1.0f, 1.0f);
    updateColorProperty("SketchVertexColor", &Client.PointColor, 1.0f, 1.0f, 1.0f);
    updateBoolProperty("ShowGrid", &Client.ShowGrid, false);
    updateBoolProperty("GridAuto", &Client.GridAuto, true);
    updateGridSize("GridSize", &Client.GridSize);
}

void ViewProviderSketch::ParameterObserver::OnChange(Base::Subject<const char*>& rCaller,
                                                     const char* sReason)
{
    (void)rCaller;

    auto key = parameterMap.find(sReason);
    if (key != parameterMap.end()) {
        auto string = key->first;
        auto update = std::get<0>(key->second);
        auto property = std::get<1>(key->second);

        update(string, property);
    }
}


/************** ViewProviderSketch::ToolManager *********************/
ViewProviderSketch::ToolManager::ToolManager(ViewProviderSketch * vp): vp(vp)
{}

std::unique_ptr<QWidget> ViewProviderSketch::ToolManager::createToolWidget() const
{
    if(vp && vp->sketchHandler) {
        return vp->sketchHandler->createToolWidget();
    }
    else {
        return nullptr;
    }
}

bool ViewProviderSketch::ToolManager::isWidgetVisible() const
{
    if(vp && vp->sketchHandler) {
        return vp->sketchHandler->isWidgetVisible();
    }
    else {
        return false;
    }
}

QPixmap ViewProviderSketch::ToolManager::getToolIcon() const
{
    if(vp && vp->sketchHandler) {
        return vp->sketchHandler->getToolIcon();
    }
    else {
        return QPixmap();
    }
}

QString ViewProviderSketch::ToolManager::getToolWidgetText() const
{
    if(vp && vp->sketchHandler) {
        return vp->sketchHandler->getToolWidgetText();
    }
    else {
        return QString();
    }
}



/*************************** ViewProviderSketch **************************/

// Struct for holding previous click information
SbTime ViewProviderSketch::DoubleClick::prvClickTime;
SbVec2s ViewProviderSketch::DoubleClick::prvClickPos;// used by double-click-detector
SbVec2s ViewProviderSketch::DoubleClick::prvCursorPos;
SbVec2s ViewProviderSketch::DoubleClick::newCursorPos;

//**************************************************************************
// Construction/Destruction

/* TRANSLATOR SketcherGui::ViewProviderSketch */

PROPERTY_SOURCE_WITH_EXTENSIONS(SketcherGui::ViewProviderSketch, PartGui::ViewProvider2DObject)


ViewProviderSketch::ViewProviderSketch()
    : SelectionObserver(false)
    , toolManager(this)
    , Mode(STATUS_NONE)
    , listener(nullptr)
    , editCoinManager(nullptr)
    , snapManager(nullptr)
    , pObserver(std::make_unique<ViewProviderSketch::ParameterObserver>(*this))
    , sketchHandler(nullptr)
    , viewOrientationFactor(1)
{
    PartGui::ViewProviderAttachExtension::initExtension(this);
    PartGui::ViewProviderGridExtension::initExtension(this);

    ADD_PROPERTY_TYPE(Autoconstraints,
                      (true),
                      "Auto Constraints",
                      (App::PropertyType)(App::Prop_None),
                      "Create auto constraints");
    ADD_PROPERTY_TYPE(AvoidRedundant,
                      (true),
                      "Auto Constraints",
                      (App::PropertyType)(App::Prop_None),
                      "Avoid redundant autoconstraint");
    ADD_PROPERTY_TYPE(
        TempoVis,
        (Py::None()),
        "Visibility automation",
        (App::PropertyType)(App::Prop_ReadOnly),
        "Object that handles hiding and showing other objects when entering/leaving sketch.");
    ADD_PROPERTY_TYPE(
        HideDependent,
        (true),
        "Visibility automation",
        (App::PropertyType)(App::Prop_ReadOnly),
        "If true, all objects that depend on the sketch are hidden when opening editing.");
    ADD_PROPERTY_TYPE(
        ShowLinks,
        (true),
        "Visibility automation",
        (App::PropertyType)(App::Prop_ReadOnly),
        "If true, all objects used in links to external geometry are shown when opening sketch.");
    ADD_PROPERTY_TYPE(
        ShowSupport,
        (true),
        "Visibility automation",
        (App::PropertyType)(App::Prop_ReadOnly),
        "If true, all objects this sketch is attached to are shown when opening sketch.");
    ADD_PROPERTY_TYPE(RestoreCamera,
                      (true),
                      "Visibility automation",
                      (App::PropertyType)(App::Prop_ReadOnly),
                      "If true, camera position before entering sketch is remembered, and restored "
                      "after closing it.");
    ADD_PROPERTY_TYPE(
        ForceOrtho,
        (false),
        "Visibility automation",
        (App::PropertyType)(App::Prop_ReadOnly),
        "If true, camera type will be forced to orthographic view when entering editing mode.");
    ADD_PROPERTY_TYPE(
        SectionView,
        (false),
        "Visibility automation",
        (App::PropertyType)(App::Prop_ReadOnly),
        "If true, only objects (or part of) located behind the sketch plane are visible.");
    ADD_PROPERTY_TYPE(EditingWorkbench,
                      ("SketcherWorkbench"),
                      "Visibility automation",
                      (App::PropertyType)(App::Prop_ReadOnly),
                      "Name of the workbench to activate when editing this sketch.");
    ADD_PROPERTY_TYPE(VisualLayerList,
                      (VisualLayer()),
                      "Layers",
                      (App::PropertyType)(App::Prop_ReadOnly),
                      "Information about the Visual Representation of layers");

    // TODO: This is part of a naive minimal implementation to substitute rendering order
    // Three equally visual layers to enable/disable layer.
    std::vector<VisualLayer> layers;
    layers.emplace_back();                // Normal layer
    layers.emplace_back(0x7E7E);          // Discontinuous line layer
    layers.emplace_back(0xFFFF, 3, false);// Hidden layer

    VisualLayerList.setValues(std::move(layers));

    // Default values that will be overridden by preferences (if existing)
    PointSize.setValue(4);

    // visibility automation and other parameters: update parameter and property defaults to follow
    // preferences
    pObserver->initParameters();
    pObserver->subscribeToParameters();

    sPixmap = "Sketcher_Sketch";

    // rubberband selection
    rubberband = std::make_unique<Gui::Rubberband>();

    cameraSensor.setFunction(&ViewProviderSketch::camSensCB);
}

ViewProviderSketch::~ViewProviderSketch()
{
    connectionToolWidget.disconnect();
}

void ViewProviderSketch::slotUndoDocument(const Gui::Document& /*doc*/)
{
    // Note 1: this slot is only operative during edit mode (see signal connection/disconnection)
    // Note 2: ViewProviderSketch::UpdateData does not generate updates during undo/redo
    //         transactions as mid-transaction data may not be in a valid state (e.g. constraints
    //         may reference invalid geometry). However undo/redo notify SketchObject after the
    //         undo/redo and before this slot is called.
    // Note 3: Note that recomputes are no longer inhibited during the call to this slot.
    forceUpdateData();
}

void ViewProviderSketch::slotRedoDocument(const Gui::Document& /*doc*/)
{
    // Note 1: this slot is only operative during edit mode (see signal connection/disconnection)
    // Note 2: ViewProviderSketch::UpdateData does not generate updates during undo/redo
    //         transactions as mid-transaction data may not be in a valid state (e.g. constraints
    //         may reference invalid geometry). However undo/redo notify SketchObject after the
    //         undo/redo and before this slot is called.
    // Note 3: Note that recomputes are no longer inhibited during the call to this slot.
    forceUpdateData();
}

void ViewProviderSketch::forceUpdateData()
{
    if (!getSketchObject()
             ->noRecomputes) {// the sketch was already solved in SketchObject in onUndoRedoFinished
        Gui::Command::updateActive();
    }
}

/***************************** handler management ************************************/

void ViewProviderSketch::activateHandler(DrawSketchHandler* newHandler)
{
    assert(editCoinManager);
    assert(!sketchHandler);

    sketchHandler = std::unique_ptr<DrawSketchHandler>(newHandler);
    Mode = STATUS_SKETCH_UseHandler;
    sketchHandler->activate(this);

    // make sure receiver has focus so immediately pressing Escape will be handled by
    // ViewProviderSketch::keyPressed() and dismiss the active handler, and not the entire
    // sketcher editor
    Gui::MDIView* mdi = Gui::Application::Instance->activeDocument()->getActiveView();
    mdi->setFocus();
}

void ViewProviderSketch::deactivateHandler()
{
    assert(isInEditMode());
    if (sketchHandler) {
        sketchHandler->deactivate();
        sketchHandler = nullptr;
    }
    Mode = STATUS_NONE;
}

/// removes the active handler
void ViewProviderSketch::purgeHandler()
{
    deactivateHandler();
    Gui::Selection().clearSelection();

    // ensure that we are in sketch only selection mode
    auto* view = dynamic_cast<Gui::View3DInventor*>(Gui::Application::Instance->editDocument()->getActiveView());

    if(view) {
        Gui::View3DInventorViewer* viewer;
        viewer = static_cast<Gui::View3DInventor*>(view)->getViewer();
        viewer->setSelectionEnabled(false);
    }
}

void ViewProviderSketch::setAxisPickStyle(bool on)
{
    assert(isInEditMode());
    editCoinManager->setAxisPickStyle(on);
}

void ViewProviderSketch::moveCursorToSketchPoint(Base::Vector2d point)
{

    SbVec3f sbpoint(point.x, point.y, 0.f);

    Gui::MDIView* mdi = this->getActiveView();
    Gui::View3DInventor* view = qobject_cast<Gui::View3DInventor*>(mdi);

    if (!view)
        return;

    Gui::View3DInventorViewer* viewer = view->getViewer();

    SbVec2s screencoords = viewer->getPointOnViewport(sbpoint);

    short x, y;
    screencoords.getValue(x, y);

    short height = viewer->getGLWidget()->height();// Coin3D origin bottom left, QT origin top left

    QPoint newPos = viewer->getGLWidget()->mapToGlobal(QPoint(x, height - y));


    // QScreen *screen = view->windowHandle()->screen();
    // QScreen *screen = QGuiApplication::primaryScreen();

    // QCursor::setPos(screen, newPos);
    QCursor::setPos(newPos);
}

void ViewProviderSketch::ensureFocus()
{

    Gui::MDIView* mdi = Gui::Application::Instance->activeDocument()->getActiveView();
           mdi->setFocus();
}

void ViewProviderSketch::preselectAtPoint(Base::Vector2d point)
{
    if (Mode != STATUS_SELECT_Point && Mode != STATUS_SELECT_Edge
        && Mode != STATUS_SELECT_Constraint && Mode != STATUS_SKETCH_DragPoint
        && Mode != STATUS_SKETCH_DragCurve && Mode != STATUS_SKETCH_DragConstraint
        && Mode != STATUS_SKETCH_UseRubberBand) {

        Gui::MDIView* mdi = this->getActiveView();
        Gui::View3DInventor* view = qobject_cast<Gui::View3DInventor*>(mdi);

        if (!view)
            return;

        Gui::View3DInventorViewer* viewer = view->getViewer();

        Base::Placement Plm = getEditingPlacement();

        auto inPlacementCoords = [&Plm](const Base::Vector3d & point) {
            Base::Vector3d pnt;
            Plm.multVec(point, pnt);
            return pnt;
        };

        auto pnt = inPlacementCoords(Base::Vector3d(point.x,point.y,0));

        SbVec3f sbpoint(static_cast<float>(pnt.x), static_cast<float>(pnt.y), static_cast<float>(pnt.z));

        SbVec2s screencoords = viewer->getPointOnViewport(sbpoint);

        std::unique_ptr<SoPickedPoint> Point(this->getPointOnRay(screencoords, viewer));

        detectAndShowPreselection(Point.get(), screencoords);
    }
}

// **********************************************************************************

bool ViewProviderSketch::keyPressed(bool pressed, int key)
{
    switch (key) {
        case SoKeyboardEvent::ESCAPE: {
            // make the handler quit but not the edit mode
            if (isInEditMode() && sketchHandler) {
                sketchHandler->registerPressedKey(pressed, key); // delegate
                return true;
            }
            if (isInEditMode() && !drag.DragConstraintSet.empty()) {
                if (!pressed) {
                    drag.DragConstraintSet.clear();
                }
                return true;
            }
            if (isInEditMode() && drag.isDragCurveValid()) {
                if (!pressed) {
                    getSketchObject()->movePoint(
                        drag.DragCurve, Sketcher::PointPos::none, Base::Vector3d(0, 0, 0), true);
                    drag.DragCurve = Drag::InvalidCurve;
                    resetPositionText();
                    Mode = STATUS_NONE;
                }
                return true;
            }
            if (isInEditMode() && drag.isDragPointValid()) {
                if (!pressed) {
                    int GeoId;
                    Sketcher::PointPos PosId;
                    getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId);
                    getSketchObject()->movePoint(GeoId, PosId, Base::Vector3d(0, 0, 0), true);
                    drag.DragPoint = Drag::InvalidPoint;
                    resetPositionText();
                    Mode = STATUS_NONE;
                }
                return true;
            }
            if (isInEditMode()) {
                // #0001479: 'Escape' key dismissing dialog cancels Sketch editing
                // If we receive a button release event but not a press event before
                // then ignore this one.
                if (!pressed && !viewProviderParameters.buttonPress)
                    return true;
                viewProviderParameters.buttonPress = pressed;

                // More control over Sketcher edit mode Esc key behavior
                // https://forum.freecad.org/viewtopic.php?f=3&t=42207
                return viewProviderParameters.handleEscapeButton;
            }
            return false;
        } break;
        default: {
            if (isInEditMode() && sketchHandler)
                sketchHandler->registerPressedKey(pressed, key);
        }
    }

    return true;// handle all other key events
}

void ViewProviderSketch::setAngleSnapping(bool enable, Base::Vector2d referencePoint)
{
    assert(snapManager);
    snapManager->setAngleSnapping(enable, referencePoint);
}

void ViewProviderSketch::getProjectingLine(const SbVec2s& pnt,
                                           const Gui::View3DInventorViewer* viewer,
                                           SbLine& line) const
{
    const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion();

    short x, y;
    pnt.getValue(x, y);
    SbVec2f VPsize = vp.getViewportSize();
    float dX, dY;
    VPsize.getValue(dX, dY);

    float fRatio = vp.getViewportAspectRatio();
    float pX = (float)x / float(vp.getViewportSizePixels()[0]);
    float pY = (float)y / float(vp.getViewportSizePixels()[1]);

    // now calculate the real points respecting aspect ratio information
    //
    if (fRatio > 1.0f) {
        pX = (pX - 0.5f * dX) * fRatio + 0.5f * dX;
    }
    else if (fRatio < 1.0f) {
        pY = (pY - 0.5f * dY) / fRatio + 0.5f * dY;
    }

    SoCamera* pCam = viewer->getSoRenderManager()->getCamera();
    if (!pCam)
        return;
    SbViewVolume vol = pCam->getViewVolume();

    vol.projectPointToLine(SbVec2f(pX, pY), line);
}

Base::Placement ViewProviderSketch::getEditingPlacement() const
{
    auto doc = Gui::Application::Instance->editDocument();
    if (!doc || doc->getInEdit() != this)
        return getSketchObject()->globalPlacement();

    // TODO: won't work if there is scale. Hmm... what to do...
    return Base::Placement(doc->getEditingTransform());
}

void ViewProviderSketch::getCoordsOnSketchPlane(const SbVec3f& point, const SbVec3f& normal,
                                                double& u, double& v) const
{
    // Plane form
    Base::Vector3d R0(0, 0, 0), RN(0, 0, 1), RX(1, 0, 0), RY(0, 1, 0);

    // move to position of Sketch
    Base::Placement Plz = getEditingPlacement();
    R0 = Plz.getPosition();
    Base::Rotation tmp(Plz.getRotation());
    tmp.multVec(RN, RN);
    tmp.multVec(RX, RX);
    tmp.multVec(RY, RY);
    Plz.setRotation(tmp);

    // line
    Base::Vector3d R1(point[0], point[1], point[2]), RA(normal[0], normal[1], normal[2]);
    if (fabs(RN * RA) < FLT_EPSILON)
        throw Base::ZeroDivisionError("View direction is parallel to sketch plane");
    // intersection point on plane
    Base::Vector3d S = R1 + ((RN * (R0 - R1)) / (RN * RA)) * RA;

    // distance to x Axle of the sketch
    S.TransformToCoordinateSystem(R0, RX, RY);

    u = S.x;
    v = S.y;
}

bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVec2s& cursorPos,
                                            const Gui::View3DInventorViewer* viewer)
{
    assert(isInEditMode());

    // Calculate 3d point to the mouse position
    SbLine line;
    getProjectingLine(cursorPos, viewer, line);
    SbVec3f point = line.getPosition();
    SbVec3f normal = line.getDirection();

    // use scoped_ptr to make sure that instance gets deleted in all cases
    boost::scoped_ptr<SoPickedPoint> pp(this->getPointOnRay(cursorPos, viewer));

    // Radius maximum to allow double click event
    const int dblClickRadius = 5;

    double x, y;
    SbVec3f pos = point;
    if (pp) {
        const SoDetail* detail = pp->getDetail();
        if (detail && detail->getTypeId() == SoPointDetail::getClassTypeId()) {
            pos = pp->getPoint();
        }
    }

    try {
        getCoordsOnSketchPlane(pos, normal, x, y);
        snapManager->snap(x, y);
    }
    catch (const Base::ZeroDivisionError&) {
        return false;
    }

    // Left Mouse button ****************************************************
    if (Button == 1) {
        if (pressed) {
            // Do things depending on the mode of the user interaction
            switch (Mode) {
                case STATUS_NONE: {
                    bool done = false;
                    if (preselection.isPreselectPointValid()) {
                        // Base::Console().Log("start dragging, point:%d\n",this->DragPoint);
                        Mode = STATUS_SELECT_Point;
                        done = true;
                    }
                    else if (preselection.isPreselectCurveValid()) {
                        // Base::Console().Log("start dragging, point:%d\n",this->DragPoint);
                        Mode = STATUS_SELECT_Edge;
                        done = true;
                    }
                    else if (preselection.isCrossPreselected()) {
                        // Base::Console().Log("start dragging, point:%d\n",this->DragPoint);
                        Mode = STATUS_SELECT_Cross;
                        done = true;
                    }
                    else if (!preselection.PreselectConstraintSet.empty()) {
                        // Base::Console().Log("start dragging, point:%d\n",this->DragPoint);
                        Mode = STATUS_SELECT_Constraint;
                        done = true;
                    }

                    // Double click events variables
                    float dci = (float)QApplication::doubleClickInterval() / 1000.0f;

                    if (done
                        && SbVec2f(cursorPos - DoubleClick::prvClickPos).length() < dblClickRadius
                        && (SbTime::getTimeOfDay() - DoubleClick::prvClickTime).getValue() < dci) {

                        // Double Click Event Occurred
                        editDoubleClicked();
                        // Reset Double Click Static Variables
                        DoubleClick::prvClickTime = SbTime();
                        DoubleClick::prvClickPos = SbVec2s(
                            -16000,
                            -16000);// certainly far away from any clickable place, to avoid
                                    // re-trigger of double-click if next click happens fast.
                        if (Mode != STATUS_SELECT_Wire) {
                            Mode = STATUS_NONE;
                        }
                    }
                    else {
                        DoubleClick::prvClickTime = SbTime::getTimeOfDay();
                        DoubleClick::prvClickPos = cursorPos;
                        DoubleClick::prvCursorPos = cursorPos;
                        DoubleClick::newCursorPos = cursorPos;
                        if (!done)
                            Mode = STATUS_SKETCH_StartRubberBand;
                    }

                    return done;
                }
                case STATUS_SKETCH_UseHandler:
                    return sketchHandler->pressButton(Base::Vector2d(x, y));
                default:
                    return false;
            }
        }
        else {// Button 1 released
            // Do things depending on the mode of the user interaction
            switch (Mode) {
                case STATUS_SELECT_Point:
                    if (pp) {
                        // Base::Console().Log("Select Point:%d\n",this->DragPoint);
                        //  Do selection
                        std::stringstream ss;
                        ss << "Vertex" << preselection.getPreselectionVertexIndex();

                        preselectToSelection(ss, pp, true);
                    }
                    Mode = STATUS_NONE;
                    return true;
                case STATUS_SELECT_Edge:
                    if (pp) {
                        // Base::Console().Log("Select Point:%d\n",this->DragPoint);
                        std::stringstream ss;
                        if (preselection.isEdge())
                            ss << "Edge" << preselection.getPreselectionEdgeIndex();
                        else// external geometry
                            ss << "ExternalEdge" << preselection.getPreselectionExternalEdgeIndex();

                        preselectToSelection(ss, pp, true);
                    }
                    Mode = STATUS_NONE;
                    return true;
                case STATUS_SELECT_Cross:
                    if (pp) {
                        // Base::Console().Log("Select Point:%d\n",this->DragPoint);
                        std::stringstream ss;
                        switch (preselection.PreselectCross) {
                            case Preselection::Axes::RootPoint:
                                ss << "RootPoint";
                                break;
                            case Preselection::Axes::HorizontalAxis:
                                ss << "H_Axis";
                                break;
                            case Preselection::Axes::VerticalAxis:
                                ss << "V_Axis";
                                break;
                            default:
                                break;
                        }

                        preselectToSelection(ss, pp, true);
                    }
                    Mode = STATUS_NONE;
                    return true;
                case STATUS_SELECT_Wire: {
                    toggleWireSelelection(preselection.PreselectCurve);
                    Mode = STATUS_NONE;
                    return true;
                }
                case STATUS_SELECT_Constraint:
                    if (pp) {
                        auto sels = preselection.PreselectConstraintSet;
                        for (int id : sels) {
                            std::stringstream ss;
                            ss << Sketcher::PropertyConstraintList::getConstraintName(id);

                            preselectToSelection(ss, pp, true);
                        }
                    }
                    Mode = STATUS_NONE;
                    return true;
                case STATUS_SKETCH_DragPoint:
                    if (drag.isDragPointValid()) {
                        int GeoId;
                        Sketcher::PointPos PosId;

                        getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId);

                        if (GeoId != Sketcher::GeoEnum::GeoUndef
                            && PosId != Sketcher::PointPos::none) {
                            getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Drag Point"));
                            try {
                                Gui::cmdAppObjectArgs(getObject(),
                                                      "movePoint(%d,%d,App.Vector(%f,%f,0),%d)",
                                                      GeoId,
                                                      static_cast<int>(PosId),
                                                      x - drag.xInit,
                                                      y - drag.yInit,
                                                      0);

                                getDocument()->commitCommand();

                                tryAutoRecomputeIfNotSolve(getSketchObject());
                            }
                            catch (const Base::Exception& e) {
                                getDocument()->abortCommand();
                                Base::Console().DeveloperError(
                                    "ViewProviderSketch", "Drag point: %s\n", e.what());
                            }
                        }
                        setPreselectPoint(drag.DragPoint);
                        drag.DragPoint = Drag::InvalidPoint;
                        // updateColor();
                    }
                    resetPositionText();
                    Mode = STATUS_NONE;
                    return true;
                case STATUS_SKETCH_DragCurve:
                    if (drag.isDragCurveValid()) {
                        const Part::Geometry* geo = getSketchObject()->getGeometry(drag.DragCurve);
                        if (geo->is<Part::GeomLineSegment>()
                            || geo->is<Part::GeomArcOfCircle>()
                            || geo->is<Part::GeomCircle>()
                            || geo->is<Part::GeomEllipse>()
                            || geo->is<Part::GeomArcOfEllipse>()
                            || geo->is<Part::GeomArcOfParabola>()
                            || geo->is<Part::GeomArcOfHyperbola>()
                            || geo->is<Part::GeomBSplineCurve>()) {
                            getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Drag Curve"));

                            auto geo = getSketchObject()->getGeometry(drag.DragCurve);
                            auto gf = GeometryFacade::getFacade(geo);

                            Base::Vector3d vec(x - drag.xInit, y - drag.yInit, 0);

                            // BSpline weights have a radius corresponding to the weight value
                            // However, in order for them proportional to the B-Spline size,
                            // the scenograph has a size scalefactor times the weight
                            // This code normalizes the information sent to the solver.
                            if (gf->getInternalType() == InternalType::BSplineControlPoint) {
                                auto circle = static_cast<const Part::GeomCircle*>(geo);
                                Base::Vector3d center = circle->getCenter();

                                Base::Vector3d dir = vec - center;

                                double scalefactor = 1.0;

                                if (circle->hasExtension(
                                        SketcherGui::ViewProviderSketchGeometryExtension::
                                            getClassTypeId())) {
                                    auto vpext = std::static_pointer_cast<
                                        const SketcherGui::ViewProviderSketchGeometryExtension>(
                                        circle
                                            ->getExtension(
                                                SketcherGui::ViewProviderSketchGeometryExtension::
                                                    getClassTypeId())
                                            .lock());

                                    scalefactor = vpext->getRepresentationFactor();
                                }

                                vec = center + dir / scalefactor;
                            }

                            try {
                                Gui::cmdAppObjectArgs(getObject(),
                                                      "movePoint(%d,%d,App.Vector(%f,%f,0),%d)",
                                                      drag.DragCurve,
                                                      static_cast<int>(Sketcher::PointPos::none),
                                                      vec.x,
                                                      vec.y,
                                                      drag.relative ? 1 : 0);

                                getDocument()->commitCommand();

                                tryAutoRecomputeIfNotSolve(getSketchObject());
                            }
                            catch (const Base::Exception& e) {
                                getDocument()->abortCommand();
                                Base::Console().DeveloperError(
                                    "ViewProviderSketch", "Drag curve: %s\n", e.what());
                            }
                        }
                        preselection.PreselectCurve = drag.DragCurve;
                        drag.DragCurve = Drag::InvalidCurve;
                        // updateColor();
                    }
                    resetPositionText();
                    Mode = STATUS_NONE;
                    return true;
                case STATUS_SKETCH_DragConstraint:
                    if (!drag.DragConstraintSet.empty()) {
                        auto idset = drag.DragConstraintSet;
                        // restore the old positions before opening the transaction and setting the new positions
                        for (int id : idset) {
                            moveConstraint(id, Base::Vector2d(drag.xInit, drag.yInit));
                        }

                        getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Drag Constraint"));
                        std::vector<Sketcher::Constraint*> constraints = getConstraints();
                        for (int id : idset) {
                            Sketcher::Constraint* constr = constraints[id]->clone();
                            moveConstraint(constr, id, Base::Vector2d(x, y));
                            constraints[id] = constr;
                        }

                        Sketcher::SketchObject* obj = getSketchObject();
                        obj->Constraints.setValues(std::move(constraints));

                        preselection.PreselectConstraintSet = drag.DragConstraintSet;
                        drag.DragConstraintSet.clear();
                        getDocument()->commitCommand();
                        tryAutoRecomputeIfNotSolve(getSketchObject());
                    }
                    Mode = STATUS_NONE;
                    return true;
                case STATUS_SKETCH_StartRubberBand:// a single click happened, so clear selection
                                                   // unless user hold control.
                    if (!(QApplication::keyboardModifiers() & Qt::ControlModifier)) {
                        Gui::Selection().clearSelection();
                    }
                    Mode = STATUS_NONE;
                    return true;
                case STATUS_SKETCH_UseRubberBand:
                    doBoxSelection(DoubleClick::prvCursorPos, cursorPos, viewer);
                    rubberband->setWorking(false);

                    // a redraw is required in order to clear the rubberband
                    draw(true, false);
                    const_cast<Gui::View3DInventorViewer*>(viewer)->redraw();
                    Mode = STATUS_NONE;
                    return true;
                case STATUS_SKETCH_UseHandler: {
                    return sketchHandler->releaseButton(Base::Vector2d(x, y));
                }
                case STATUS_NONE:
                default:
                    return false;
            }
        }
    }
    // Right mouse button ****************************************************
    else if (Button == 2) {
        if (pressed) {
            // Do things depending on the mode of the user interaction
            switch (Mode) {
                case STATUS_NONE: {
                    if (preselection.isPreselectPointValid()) {
                        // Base::Console().Log("start dragging, point:%d\n",this->DragPoint);
                        Mode = STATUS_SELECT_Point;
                    }
                    else if (preselection.isPreselectCurveValid()) {
                        // Base::Console().Log("start dragging, point:%d\n",this->DragPoint);
                        Mode = STATUS_SELECT_Edge;
                    }
                    else if (preselection.isCrossPreselected()) {
                        // Base::Console().Log("start dragging, point:%d\n",this->DragPoint);
                        Mode = STATUS_SELECT_Cross;
                    }
                    else if (!preselection.PreselectConstraintSet.empty()) {
                        // Base::Console().Log("start dragging, point:%d\n",this->DragPoint);
                        Mode = STATUS_SELECT_Constraint;
                    }
                }
                default:
                    break;
            }
        }
        else if (!pressed) {
            switch (Mode) {
                case STATUS_SKETCH_UseHandler:
                    // delegate to handler whether to quit or do otherwise
                    sketchHandler->pressRightButton(Base::Vector2d(x, y));
                    return true;
                case STATUS_NONE:
                    generateContextMenu();
                    return true;
                case STATUS_SELECT_Point:
                    if (pp) {
                        // Base::Console().Log("Select Point:%d\n",this->DragPoint);
                        //  Do selection
                        std::stringstream ss;
                        ss << "Vertex" << preselection.getPreselectionVertexIndex();

                        preselectToSelection(ss, pp, false);
                    }
                    Mode = STATUS_NONE;
                    generateContextMenu();
                    return true;
                case STATUS_SELECT_Edge:
                    if (pp) {
                        // Base::Console().Log("Select Point:%d\n",this->DragPoint);
                        std::stringstream ss;
                        if (preselection.isEdge()) {
                            ss << "Edge" << preselection.getPreselectionEdgeIndex();
                        }
                        else {  // external geometry
                            ss << "ExternalEdge" << preselection.getPreselectionExternalEdgeIndex();
                        }

                        preselectToSelection(ss, pp, false);
                    }
                    Mode = STATUS_NONE;
                    generateContextMenu();
                    return true;
                case STATUS_SELECT_Cross:
                    if (pp) {
                        // Base::Console().Log("Select Point:%d\n",this->DragPoint);
                        std::stringstream ss;
                        switch (preselection.PreselectCross) {
                            case Preselection::Axes::RootPoint:
                                ss << "RootPoint";
                                break;
                            case Preselection::Axes::HorizontalAxis:
                                ss << "H_Axis";
                                break;
                            case Preselection::Axes::VerticalAxis:
                                ss << "V_Axis";
                                break;
                            default:
                                break;
                        }

                        preselectToSelection(ss, pp, false);
                    }
                    Mode = STATUS_NONE;
                    generateContextMenu();
                    return true;
                case STATUS_SELECT_Constraint: {
                    if (pp) {
                        auto sels = preselection.PreselectConstraintSet;
                        for (int id : sels) {
                            std::stringstream ss;
                            ss << Sketcher::PropertyConstraintList::getConstraintName(id);

                            preselectToSelection(ss, pp, false);
                        }
                    }
                    Mode = STATUS_NONE;
                    generateContextMenu();
                    return true;
                }
                case STATUS_SKETCH_DragPoint:
                case STATUS_SKETCH_DragCurve:
                case STATUS_SKETCH_DragConstraint:
                case STATUS_SKETCH_StartRubberBand:
                case STATUS_SKETCH_UseRubberBand:
                case STATUS_SELECT_Wire:
                    break;
            }
        }
    }

    return false;
}

bool ViewProviderSketch::mouseWheelEvent(int delta, const SbVec2s& cursorPos,
                                         const Gui::View3DInventorViewer* viewer)
{
    assert(isInEditMode());

    Q_UNUSED(delta);
    Q_UNUSED(cursorPos);
    Q_UNUSED(viewer);

    editCoinManager->drawConstraintIcons();

    return true;
}

void ViewProviderSketch::editDoubleClicked()
{
    if (preselection.isPreselectPointValid()) {
        Base::Console().Log("double click point:%d\n", preselection.PreselectPoint);
    }
    else if (preselection.isPreselectCurveValid()) {
        // We cannot do toggleWireSelelection directly here because the released event with
        //STATUS_NONE return false which clears the selection.
        Mode = STATUS_SELECT_Wire;
    }
    else if (preselection.isCrossPreselected()) {
        Base::Console().Log("double click cross:%d\n",
                            static_cast<int>(preselection.PreselectCross));
    }
    else if (!preselection.PreselectConstraintSet.empty()) {
        // Find the constraint
        const std::vector<Sketcher::Constraint*>& constrlist =
            getSketchObject()->Constraints.getValues();

        auto sels = preselection.PreselectConstraintSet;
        for (int id : sels) {

            Constraint* Constr = constrlist[id];

            // if its the right constraint
            if (Constr->isDimensional()) {
                Gui::Command::openCommand(
                    QT_TRANSLATE_NOOP("Command", "Modify sketch constraints"));
                EditDatumDialog editDatumDialog(this, id);
                editDatumDialog.exec();
            }
        }
    }
}

void ViewProviderSketch::toggleWireSelelection(int clickedGeoId)
{
    Sketcher::SketchObject* obj = getSketchObject();

    const Part::Geometry* geo1 = obj->getGeometry(clickedGeoId);
    if (isPoint(*geo1) || isCircle(*geo1) || isEllipse(*geo1) || isPeriodicBSplineCurve(*geo1)) {
        return;
    }

    const char* type1 = (clickedGeoId >= 0) ? "Edge" : "ExternalEdge";
    std::stringstream ss1;
    ss1 << type1 << clickedGeoId + 1;
    bool selecting = isSelected(ss1.str());

    std::vector<int> connectedEdges = { clickedGeoId };
    bool partHasBeenAdded = true;
    while (partHasBeenAdded) {
        partHasBeenAdded = false;
        for (int geoId = 0; geoId <= obj->getHighestCurveIndex(); geoId++) {
            if (geoId == clickedGeoId || std::find(connectedEdges.begin(), connectedEdges.end(), geoId) != connectedEdges.end()) {
                continue;
            }

            const Part::Geometry* geo = obj->getGeometry(geoId);
            if (isPoint(*geo) || isCircle(*geo) || isEllipse(*geo) || isPeriodicBSplineCurve(*geo1)) {
                continue;
            }

            Base::Vector3d p11 = obj->getPoint(geoId, PointPos::start);
            Base::Vector3d p12 = obj->getPoint(geoId, PointPos::end);
            bool connected = false;
            for (auto conGeoId : connectedEdges) {
                Base::Vector3d p21 = obj->getPoint(conGeoId, PointPos::start);
                Base::Vector3d p22 = obj->getPoint(conGeoId, PointPos::end);
                if ((p11 - p21).Length() < Precision::Confusion()
                    || (p11 - p22).Length() < Precision::Confusion()
                    || (p12 - p21).Length() < Precision::Confusion()
                    || (p12 - p22).Length() < Precision::Confusion()) {
                    connected = true;
                }
            }

            if (connected) {
                connectedEdges.push_back(geoId);
                partHasBeenAdded = true;
                break;
            }
        }
    }

    for (auto geoId : connectedEdges) {
        std::stringstream ss;
        const char* type = (geoId >= 0) ? "Edge" : "ExternalEdge";
        ss << type << geoId + 1;
        if (!selecting && isSelected(ss.str())) {
            rmvSelection(ss.str());
        }
        else if (selecting && !isSelected(ss.str())) {
            addSelection2(ss.str());
        }
    }

}

bool ViewProviderSketch::mouseMove(const SbVec2s& cursorPos, Gui::View3DInventorViewer* viewer)
{
    // maximum radius for mouse moves when selecting a geometry before switching to drag mode
    const int dragIgnoredDistance = 3;

    static bool selectableConstraints = true;

    if (Mode < STATUS_SKETCH_UseHandler) {
        bool tmpSelCons = QApplication::keyboardModifiers() & Qt::ShiftModifier;
        if (tmpSelCons != !selectableConstraints) {
            selectableConstraints = !tmpSelCons;
            editCoinManager->setConstraintSelectability(selectableConstraints);
        }
    }

    if (!isInEditMode())
        return false;

    // ignore small moves after selection
    switch (Mode) {
        case STATUS_SELECT_Point:
        case STATUS_SELECT_Edge:
        case STATUS_SELECT_Constraint:
        case STATUS_SKETCH_StartRubberBand:
            short dx, dy;
            (cursorPos - DoubleClick::prvCursorPos).getValue(dx, dy);
            if (std::abs(dx) < dragIgnoredDistance && std::abs(dy) < dragIgnoredDistance)
                return false;
        default:
            break;
    }

    // Calculate 3d point to the mouse position
    SbLine line;
    getProjectingLine(cursorPos, viewer, line);

    double x, y;
    try {
        getCoordsOnSketchPlane(line.getPosition(), line.getDirection(), x, y);
        snapManager->snap(x, y);
    }
    catch (const Base::ZeroDivisionError&) {
        return false;
    }

    bool preselectChanged = false;
    if (Mode != STATUS_SELECT_Point && Mode != STATUS_SELECT_Edge
        && Mode != STATUS_SELECT_Constraint && Mode != STATUS_SKETCH_DragPoint
        && Mode != STATUS_SKETCH_DragCurve && Mode != STATUS_SKETCH_DragConstraint
        && Mode != STATUS_SKETCH_UseRubberBand) {

        std::unique_ptr<SoPickedPoint> Point(this->getPointOnRay(cursorPos, viewer));

        preselectChanged = detectAndShowPreselection(Point.get(), cursorPos);
    }

    switch (Mode) {
        case STATUS_NONE:
            if (preselectChanged) {
                editCoinManager->drawConstraintIcons();
                this->updateColor();
                return true;
            }
            return false;
        case STATUS_SELECT_Point:
            if (!getSolvedSketch().hasConflicts() && preselection.isPreselectPointValid()
                && drag.DragPoint != preselection.PreselectPoint) {
                Mode = STATUS_SKETCH_DragPoint;
                drag.DragPoint = preselection.PreselectPoint;
                int GeoId;
                Sketcher::PointPos PosId;

                getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId);

                if (GeoId != Sketcher::GeoEnum::GeoUndef && PosId != Sketcher::PointPos::none) {
                    getSketchObject()->initTemporaryMove(GeoId, PosId, false);
                    drag.resetVector();
                }
            }
            else {
                Mode = STATUS_NONE;
            }
            resetPreselectPoint();
            return true;
        case STATUS_SELECT_Edge:
            if (!getSolvedSketch().hasConflicts() && preselection.isPreselectCurveValid()
                && drag.DragCurve != preselection.PreselectCurve) {
                Mode = STATUS_SKETCH_DragCurve;
                drag.DragCurve = preselection.PreselectCurve;
                const Part::Geometry* geo = getSketchObject()->getGeometry(drag.DragCurve);

                // BSpline Control points are edge draggable only if their radius is movable
                // This is because dragging gives unwanted cosmetic results due to the scale ratio.
                // This is an heuristic as it does not check all indirect routes.
                if (GeometryFacade::isInternalType(geo, InternalType::BSplineControlPoint)) {
                    if (geo->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) {
                        auto solvext =
                            std::static_pointer_cast<const Sketcher::SolverGeometryExtension>(
                                geo->getExtension(
                                       Sketcher::SolverGeometryExtension::getClassTypeId())
                                    .lock());

                        // Edge parameters are Independent, so weight won't move
                        if (solvext->getEdge() == Sketcher::SolverGeometryExtension::Independent) {
                            Mode = STATUS_NONE;
                            return false;
                        }

                        // The B-Spline is constrained to be non-rational (equal weights), moving
                        // produces a bad effect because OCCT will normalize the values of the
                        // weights.
                        auto grp = getSolvedSketch().getDependencyGroup(drag.DragCurve,
                                                                        Sketcher::PointPos::none);

                        int bsplinegeoid = -1;

                        std::vector<int> polegeoids;

                        for (auto c : getSketchObject()->Constraints.getValues()) {
                            if (c->Type == Sketcher::InternalAlignment
                                && c->AlignmentType == BSplineControlPoint
                                && c->First == drag.DragCurve) {

                                bsplinegeoid = c->Second;
                                break;
                            }
                        }

                        if (bsplinegeoid == -1) {
                            Mode = STATUS_NONE;
                            return false;
                        }

                        for (auto c : getSketchObject()->Constraints.getValues()) {
                            if (c->Type == Sketcher::InternalAlignment
                                && c->AlignmentType == BSplineControlPoint
                                && c->Second == bsplinegeoid) {

                                polegeoids.push_back(c->First);
                            }
                        }

                        bool allingroup = true;

                        for (auto polegeoid : polegeoids) {
                            std::pair<int, Sketcher::PointPos> thispole =
                                std::make_pair(polegeoid, Sketcher::PointPos::none);

                            if (grp.find(thispole) == grp.end())// not found
                                allingroup = false;
                        }

                        if (allingroup) {// it is constrained to be non-rational
                            Mode = STATUS_NONE;
                            return false;
                        }
                    }
                }

                if (geo->is<Part::GeomLineSegment>()
                    || geo->is<Part::GeomBSplineCurve>()) {
                    drag.relative = true;

                    // Since the cursor moved from where it was clicked, and this is a relative
                    // move, calculate the click position and use it as initial point.
                    SbLine line2;
                    getProjectingLine(DoubleClick::prvCursorPos, viewer, line2);
                    getCoordsOnSketchPlane(
                        line2.getPosition(), line2.getDirection(), drag.xInit, drag.yInit);
                    snapManager->snap(drag.xInit, drag.yInit);
                }
                else {
                    drag.resetVector();
                }

                if (geo->is<Part::GeomBSplineCurve>()) {
                    getSketchObject()->initTemporaryBSplinePieceMove(
                        drag.DragCurve,
                        Sketcher::PointPos::none,
                        Base::Vector3d(drag.xInit, drag.yInit, 0.0),
                        false);
                }
                else {
                    getSketchObject()->initTemporaryMove(
                        drag.DragCurve, Sketcher::PointPos::none, false);
                }
            }
            else {
                Mode = STATUS_NONE;
            }
            resetPreselectPoint();
            return true;
        case STATUS_SELECT_Constraint:
            Mode = STATUS_SKETCH_DragConstraint;
            drag.DragConstraintSet = preselection.PreselectConstraintSet;
            drag.xInit = x;
            drag.yInit = y;
            resetPreselectPoint();
            return true;
        case STATUS_SKETCH_DragPoint:
            if (drag.isDragPointValid()) {
                // Base::Console().Log("Drag Point:%d\n",edit->DragPoint);
                int GeoId;
                Sketcher::PointPos PosId;
                getSketchObject()->getGeoVertexIndex(drag.DragPoint, GeoId, PosId);
                Base::Vector3d vec(x, y, 0);

                if (GeoId != Sketcher::GeoEnum::GeoUndef && PosId != Sketcher::PointPos::none) {
                    if (getSketchObject()->moveTemporaryPoint(GeoId, PosId, vec, false) == 0) {
                        setPositionText(Base::Vector2d(x, y));
                        draw(true, false);
                    }
                }
            }
            return true;
        case STATUS_SKETCH_DragCurve:
            if (drag.isDragCurveValid()) {
                auto geo = getSketchObject()->getGeometry(drag.DragCurve);
                auto gf = GeometryFacade::getFacade(geo);

                Base::Vector3d vec(x - drag.xInit, y - drag.yInit, 0);

                // BSpline weights have a radius corresponding to the weight value
                // However, in order for them proportional to the B-Spline size,
                // the scenograph has a size scalefactor times the weight
                // This code normalizes the information sent to the solver.
                if (gf->getInternalType() == InternalType::BSplineControlPoint) {
                    auto circle = static_cast<const Part::GeomCircle*>(geo);
                    Base::Vector3d center = circle->getCenter();

                    Base::Vector3d dir = vec - center;

                    double scalefactor = 1.0;

                    if (circle->hasExtension(
                            SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) {
                        auto vpext = std::static_pointer_cast<
                            const SketcherGui::ViewProviderSketchGeometryExtension>(
                            circle
                                ->getExtension(SketcherGui::ViewProviderSketchGeometryExtension::
                                                   getClassTypeId())
                                .lock());

                        scalefactor = vpext->getRepresentationFactor();
                    }

                    vec = center + dir / scalefactor;
                }

                if (getSketchObject()->moveTemporaryPoint(
                        drag.DragCurve, Sketcher::PointPos::none, vec, drag.relative)
                    == 0) {
                    setPositionText(Base::Vector2d(x, y));
                    draw(true, false);
                }
            }
            return true;
        case STATUS_SKETCH_DragConstraint:
            if (!drag.DragConstraintSet.empty()) {
                auto idset = drag.DragConstraintSet;
                for (int id : idset) {
                    moveConstraint(id, Base::Vector2d(x, y));
                }
            }
            return true;
        case STATUS_SKETCH_UseHandler:
            sketchHandler->mouseMove(Base::Vector2d(x, y));
            if (preselectChanged) {
                editCoinManager->drawConstraintIcons();
                this->updateColor();
            }
            return true;
        case STATUS_SKETCH_StartRubberBand: {
            Mode = STATUS_SKETCH_UseRubberBand;
            rubberband->setWorking(true);
            return true;
        }
        case STATUS_SKETCH_UseRubberBand: {
            // Here we must use the device-pixel-ratio to compute the correct y coordinate
            // (#0003130)
            qreal dpr = viewer->getGLWidget()->devicePixelRatioF();
            DoubleClick::newCursorPos = cursorPos;
            rubberband->setCoords(
                DoubleClick::prvCursorPos.getValue()[0],
                viewer->getGLWidget()->height() * dpr - DoubleClick::prvCursorPos.getValue()[1],
                DoubleClick::newCursorPos.getValue()[0],
                viewer->getGLWidget()->height() * dpr - DoubleClick::newCursorPos.getValue()[1]);
            viewer->redraw();
            return true;
        }
        default:
            return false;
    }

    return false;
}

void ViewProviderSketch::moveConstraint(int constNum, const Base::Vector2d& toPos)
{
    if (auto constr = getConstraint(constNum)) {
        moveConstraint(constr, constNum, toPos);
    }
}

void ViewProviderSketch::moveConstraint(Sketcher::Constraint* Constr, int constNum, const Base::Vector2d& toPos)
{
    // are we in edit?
    if (!isInEditMode())
        return;

#ifdef FC_DEBUG
    Sketcher::SketchObject* obj = getSketchObject();
    int intGeoCount = obj->getHighestCurveIndex() + 1;
    int extGeoCount = obj->getExternalGeometryCount();
#endif

    // with memory allocation
    const std::vector<Part::Geometry*> geomlist = getSolvedSketch().extractGeometry(true, true);

#ifdef FC_DEBUG
    assert(int(geomlist.size()) == extGeoCount + intGeoCount);
    assert((Constr->First >= -extGeoCount && Constr->First < intGeoCount)
           || Constr->First != GeoEnum::GeoUndef);
#endif

    if (Constr->Type == Distance || Constr->Type == DistanceX || Constr->Type == DistanceY
        || Constr->Type == Radius || Constr->Type == Diameter || Constr->Type == Weight) {

        Base::Vector3d p1(0., 0., 0.), p2(0., 0., 0.);
        if (Constr->SecondPos != Sketcher::PointPos::none) {// point to point distance
            p1 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos);
            p2 = getSolvedSketch().getPoint(Constr->Second, Constr->SecondPos);
        }
        else if (Constr->Second != GeoEnum::GeoUndef) {
            p1 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos);
            const Part::Geometry *geo1 = GeoList::getGeometryFromGeoId (geomlist, Constr->First);
            const Part::Geometry *geo2 = GeoList::getGeometryFromGeoId (geomlist, Constr->Second);

            if (isLineSegment(*geo2)) {
                if (isCircleOrArc(*geo1)){
                    std::swap(geo1, geo2); // see below
                }
                else {
                    // point to line distance
                    auto lineSeg = static_cast<const Part::GeomLineSegment *>(geo2); //NOLINT
                    Base::Vector3d l2p1 = lineSeg->getStartPoint();
                    Base::Vector3d l2p2 = lineSeg->getEndPoint();
                    // calculate the projection of p1 onto line2
                    p2.ProjectToLine(p1-l2p1, l2p2-l2p1);
                    p2 += p1;
                }
            }

            if (isCircleOrArc(*geo2)) {
                if (Constr->FirstPos != Sketcher::PointPos::none){ // circular to point distance
                    auto [rad, ct] = getRadiusCenterCircleArc(geo2);

                    Base::Vector3d v = p1 - ct;
                    v = v.Normalize();
                    p2 = ct + rad * v;
                }
                else if (isCircleOrArc(*geo1)) { // circular to circular distance
                    GetCirclesMinimalDistance(geo1, geo2, p1, p2);
                }
                else if (isLineSegment(*geo1)){ // circular to line distance
                    auto lineSeg = static_cast<const Part::GeomLineSegment*>(geo1); //NOLINT
                    Base::Vector3d l2p1 = lineSeg->getStartPoint();
                    Base::Vector3d l2p2 = lineSeg->getEndPoint();

                    auto [rad, ct] = getRadiusCenterCircleArc(geo2);

                    p1.ProjectToLine(ct - l2p1, l2p2 - l2p1);// project on the line translated to origin
                    Base::Vector3d v = p1;
                    p1 += ct;
                    v.Normalize();
                    p2 = ct + v * rad;
                }
            }
        }
        else if (Constr->FirstPos != Sketcher::PointPos::none) {
            p2 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos);
        }
        else if (Constr->First != GeoEnum::GeoUndef) {
            const Part::Geometry* geo = GeoList::getGeometryFromGeoId(geomlist, Constr->First);
            if (geo->is<Part::GeomLineSegment>()) {
                const Part::GeomLineSegment* lineSeg =
                    static_cast<const Part::GeomLineSegment*>(geo);
                p1 = lineSeg->getStartPoint();
                p2 = lineSeg->getEndPoint();
            }
            else if (geo->is<Part::GeomArcOfCircle>()) {
                const Part::GeomArcOfCircle* arc = static_cast<const Part::GeomArcOfCircle*>(geo);
                double radius = arc->getRadius();
                Base::Vector3d center = arc->getCenter();
                p1 = center;

                double angle = Constr->LabelPosition;
                if (angle == 10) {
                    double startangle, endangle;
                    arc->getRange(startangle, endangle, /*emulateCCW=*/true);
                    angle = (startangle + endangle) / 2;
                }
                else {
                    Base::Vector3d tmpDir = Base::Vector3d(toPos.x, toPos.y, 0) - p1;
                    angle = atan2(tmpDir.y, tmpDir.x);
                }

                if (Constr->Type == Sketcher::Diameter)
                    p1 = center - radius * Base::Vector3d(cos(angle), sin(angle), 0.);

                p2 = center + radius * Base::Vector3d(cos(angle), sin(angle), 0.);
            }
            else if (geo->is<Part::GeomCircle>()) {
                const Part::GeomCircle* circle = static_cast<const Part::GeomCircle*>(geo);
                double radius = circle->getRadius();
                Base::Vector3d center = circle->getCenter();
                p1 = center;

                Base::Vector3d tmpDir = Base::Vector3d(toPos.x, toPos.y, 0) - p1;

                Base::Vector3d dir = radius * tmpDir.Normalize();

                if (Constr->Type == Sketcher::Diameter)
                    p1 = center - dir;

                if (Constr->Type == Sketcher::Weight) {

                    double scalefactor = 1.0;

                    if (circle->hasExtension(
                            SketcherGui::ViewProviderSketchGeometryExtension::getClassTypeId())) {
                        auto vpext = std::static_pointer_cast<
                            const SketcherGui::ViewProviderSketchGeometryExtension>(
                            circle
                                ->getExtension(SketcherGui::ViewProviderSketchGeometryExtension::
                                                   getClassTypeId())
                                .lock());

                        scalefactor = vpext->getRepresentationFactor();
                    }

                    p2 = center + dir * scalefactor;
                }
                else
                    p2 = center + dir;
            }
            else
                return;
        }
        else
            return;

        Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p2;

        Base::Vector3d dir;
        if (Constr->Type == Distance || Constr->Type == Radius || Constr->Type == Diameter
            || Constr->Type == Weight)
            dir = (p2 - p1).Normalize();
        else if (Constr->Type == DistanceX)
            dir = Base::Vector3d((p2.x - p1.x >= FLT_EPSILON) ? 1 : -1, 0, 0);
        else if (Constr->Type == DistanceY)
            dir = Base::Vector3d(0, (p2.y - p1.y >= FLT_EPSILON) ? 1 : -1, 0);

        if (Constr->Type == Radius || Constr->Type == Diameter || Constr->Type == Weight) {
            Constr->LabelDistance = vec.x * dir.x + vec.y * dir.y;
            Constr->LabelPosition = atan2(dir.y, dir.x);
        }
        else {
            Base::Vector3d normal(-dir.y, dir.x, 0);
            Constr->LabelDistance = vec.x * normal.x + vec.y * normal.y;
            if (Constr->Type == Distance || Constr->Type == DistanceX
                || Constr->Type == DistanceY) {
                vec = Base::Vector3d(toPos.x, toPos.y, 0) - (p2 + p1) / 2;
                Constr->LabelPosition = vec.x * dir.x + vec.y * dir.y;
            }
        }
    }
    else if (Constr->Type == Angle) {
        moveAngleConstraint(Constr, constNum, toPos);
    }

    // delete the cloned objects
    for (Part::Geometry* geomPtr : geomlist) {
        if (geomPtr) {
            delete geomPtr;
        }
    }

    draw(true, false);
}

void ViewProviderSketch::moveAngleConstraint(Sketcher::Constraint* constr, int constNum, const Base::Vector2d& toPos)
{
    Sketcher::SketchObject* obj = getSketchObject();
    Base::Vector3d p0(0., 0., 0.);
    double factor = 0.5;
    if (constr->Second != GeoEnum::GeoUndef) {// line to line angle
        if (constr->Third == GeoEnum::GeoUndef) {// angle between two lines
            const Part::Geometry* geo1 = obj->getGeometry(constr->First);
            const Part::Geometry* geo2 = obj->getGeometry(constr->Second);

            if (!isLineSegment(*geo1) || !isLineSegment(*geo2)) {
                return;
            }
            const auto* lineSeg1 = static_cast<const Part::GeomLineSegment*>(geo1);
            const auto* lineSeg2 = static_cast<const Part::GeomLineSegment*>(geo2);

            Base::Vector2d l1[2], l2[2];
            l1[0] = Base::Vector2d(lineSeg1->getStartPoint().x, lineSeg1->getStartPoint().y);
            l1[1] = Base::Vector2d(lineSeg1->getEndPoint().x, lineSeg1->getEndPoint().y);
            l2[0] = Base::Vector2d(lineSeg2->getStartPoint().x, lineSeg2->getStartPoint().y);
            l2[1] = Base::Vector2d(lineSeg2->getEndPoint().x, lineSeg2->getEndPoint().y);

            // First we will check if the angle needs to be reversed to its supplementary
            bool flip1 = (constr->FirstPos == Sketcher::PointPos::end);
            bool flip2 = (constr->SecondPos == Sketcher::PointPos::end);
            Base::Vector2d p11 = flip1 ? l1[1] : l1[0];
            Base::Vector2d p12 = flip1 ? l1[0] : l1[1];
            Base::Vector2d p21 = flip2 ? l2[1] : l2[0];
            Base::Vector2d p22 = flip2 ? l2[0] : l2[1];

            // Get the intersection point in 2d of the two lines if possible
            Base::Line2d line1(p11, p12);
            Base::Line2d line2(p21, p22);
            Base::Vector2d intersection = Base::Vector2d(0., 0.);
            if (!line1.Intersect(line2, intersection)) {
                return;
            }

            Base::Vector2d dir1 = p12 - p11;
            Base::Vector2d dir2 = p22 - p21;

            Base::Vector2d ap3 = intersection + dir1 + dir2;

            auto isLeftOfLine = [](Base::Vector2d a, Base::Vector2d b, Base::Vector2d c) {
                return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x) > 0;
            };

            bool sign1 = isLeftOfLine(p11, p12, ap3);
            bool sign2 = isLeftOfLine(p21, p22, ap3);

            bool sign3 = isLeftOfLine(p11, p12, toPos);
            bool sign4 = isLeftOfLine(p21, p22, toPos);

            bool reverse = !(sign1 == sign3 && sign2 == sign4) && !(sign1 != sign3 && sign2 != sign4);

            if (reverse) {
                obj->reverseAngleConstraintToSupplementary(constr, constNum);

                ap3 = intersection + dir1 - dir2; //- dir2 instead of std::swap(dir1, dir2) and dir1 = -dir1
                sign1 = isLeftOfLine(p11, p12, ap3);
                sign2 = isLeftOfLine(p21, p22, ap3);
            }

            bool inverse = !(sign1 == sign3 && sign2 == sign4);
            if (inverse) {
                obj->inverseAngleConstraint(constr);
            }

            p0 = Base::Vector3d(intersection.x, intersection.y, 0.);
        }
        else {// angle-via-point
            Base::Vector3d p = getSolvedSketch().getPoint(constr->Third, constr->ThirdPos);
            p0 = Base::Vector3d(p.x, p.y, 0);
            Base::Vector3d dir1 = getSolvedSketch().calculateNormalAtPoint(constr->First, p.x, p.y);
            dir1.RotateZ(-M_PI / 2);// convert to vector of tangency by rotating
            Base::Vector3d dir2 = getSolvedSketch().calculateNormalAtPoint(constr->Second, p.x, p.y);
            dir2.RotateZ(-M_PI / 2);

            Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p0;
            factor = factor * Base::sgn<double>((dir1 + dir2) * vec);
        }
    }
    else if (constr->First != GeoEnum::GeoUndef) {// line/arc angle
        const Part::Geometry* geo = obj->getGeometry(constr->First);

        if (isLineSegment(*geo)) {
            const auto* lineSeg = static_cast<const Part::GeomLineSegment*>(geo);
            p0 = (lineSeg->getEndPoint() + lineSeg->getStartPoint()) / 2;
        }
        else if (isArcOfCircle(*geo)) {
            const auto* arc = static_cast<const Part::GeomArcOfCircle*>(geo);
            p0 = arc->getCenter();
        }
        else {
            return;
        }
    }
    else {
        return;
    }

    Base::Vector3d vec = Base::Vector3d(toPos.x, toPos.y, 0) - p0;
    constr->LabelDistance = factor * vec.Length();
}

bool ViewProviderSketch::isSelectable() const
{
    if (isEditing())
        return false;
    else
        return PartGui::ViewProvider2DObject::isSelectable();
}

void ViewProviderSketch::onSelectionChanged(const Gui::SelectionChanges& msg)
{
    // are we in edit?
    if (isInEditMode()) {
        // ignore external object
        if (!msg.Object.getObjectName().empty()
            && msg.Object.getDocument() != getObject()->getDocument())
            return;

        bool handled = false;
        if (Mode == STATUS_SKETCH_UseHandler) {
            App::AutoTransaction committer;
            handled = sketchHandler->onSelectionChanged(msg);
        }
        if (handled)
            return;

        std::string temp;
        if (msg.Type == Gui::SelectionChanges::ClrSelection) {
            // if something selected in this object?
            if (!selection.SelPointSet.empty() || !selection.SelCurvSet.empty()
                || !selection.SelConstraintSet.empty()) {
                // clear our selection and update the color of the viewed edges and points
                clearSelectPoints();
                selection.SelCurvSet.clear();
                selection.SelConstraintSet.clear();
                editCoinManager->drawConstraintIcons();
                this->updateColor();
            }
        }
        else if (msg.Type == Gui::SelectionChanges::AddSelection) {
            // is it this object??
            if (strcmp(msg.pDocName, getSketchObject()->getDocument()->getName()) == 0
                && strcmp(msg.pObjectName, getSketchObject()->getNameInDocument()) == 0) {
                if (msg.pSubName) {
                    std::string shapetype(msg.pSubName);
                    if (shapetype.size() > 4 && shapetype.substr(0, 4) == "Edge") {
                        int GeoId = std::atoi(&shapetype[4]) - 1;
                        selection.SelCurvSet.insert(GeoId);
                        this->updateColor();
                    }
                    else if (shapetype.size() > 12 && shapetype.substr(0, 12) == "ExternalEdge") {
                        int GeoId = std::atoi(&shapetype[12]) - 1;
                        GeoId = -GeoId - 3;
                        selection.SelCurvSet.insert(GeoId);
                        this->updateColor();
                    }
                    else if (shapetype.size() > 6 && shapetype.substr(0, 6) == "Vertex") {
                        int VtId = std::atoi(&shapetype[6]) - 1;
                        addSelectPoint(VtId);
                        this->updateColor();
                    }
                    else if (shapetype == "RootPoint") {
                        addSelectPoint(Selection::RootPoint);
                        this->updateColor();
                    }
                    else if (shapetype == "H_Axis") {
                        selection.SelCurvSet.insert(Selection::HorizontalAxis);
                        this->updateColor();
                    }
                    else if (shapetype == "V_Axis") {
                        selection.SelCurvSet.insert(Selection::VerticalAxis);
                        this->updateColor();
                    }
                    else if (shapetype.size() > 10 && shapetype.substr(0, 10) == "Constraint") {
                        int ConstrId =
                            Sketcher::PropertyConstraintList::getIndexFromConstraintName(shapetype);
                        selection.SelConstraintSet.insert(ConstrId);
                        editCoinManager->drawConstraintIcons();
                        this->updateColor();
                    }
                }
            }
        }
        else if (msg.Type == Gui::SelectionChanges::RmvSelection) {
            // Are there any objects selected
            if (!selection.SelPointSet.empty() || !selection.SelCurvSet.empty()
                || !selection.SelConstraintSet.empty()) {
                // is it this object??
                if (strcmp(msg.pDocName, getSketchObject()->getDocument()->getName()) == 0
                    && strcmp(msg.pObjectName, getSketchObject()->getNameInDocument()) == 0) {
                    if (msg.pSubName) {
                        std::string shapetype(msg.pSubName);
                        if (shapetype.size() > 4 && shapetype.substr(0, 4) == "Edge") {
                            int GeoId = std::atoi(&shapetype[4]) - 1;
                            selection.SelCurvSet.erase(GeoId);
                            this->updateColor();
                        }
                        else if (shapetype.size() > 12
                                 && shapetype.substr(0, 12) == "ExternalEdge") {
                            int GeoId = std::atoi(&shapetype[12]) - 1;
                            GeoId = -GeoId - 3;
                            selection.SelCurvSet.erase(GeoId);
                            this->updateColor();
                        }
                        else if (shapetype.size() > 6 && shapetype.substr(0, 6) == "Vertex") {
                            int VtId = std::atoi(&shapetype[6]) - 1;
                            removeSelectPoint(VtId);
                            this->updateColor();
                        }
                        else if (shapetype == "RootPoint") {
                            removeSelectPoint(Sketcher::GeoEnum::RtPnt);
                            this->updateColor();
                        }
                        else if (shapetype == "H_Axis") {
                            selection.SelCurvSet.erase(Sketcher::GeoEnum::HAxis);
                            this->updateColor();
                        }
                        else if (shapetype == "V_Axis") {
                            selection.SelCurvSet.erase(Sketcher::GeoEnum::VAxis);
                            this->updateColor();
                        }
                        else if (shapetype.size() > 10 && shapetype.substr(0, 10) == "Constraint") {
                            int ConstrId =
                                Sketcher::PropertyConstraintList::getIndexFromConstraintName(
                                    shapetype);
                            selection.SelConstraintSet.erase(ConstrId);
                            editCoinManager->drawConstraintIcons();
                            this->updateColor();
                        }
                    }
                }
            }
        }
        else if (msg.Type == Gui::SelectionChanges::SetSelection) {
            // remove all items
            // selectionView->clear();
            // std::vector<SelectionSingleton::SelObj> objs =
            // Gui::Selection().getSelection(Reason.pDocName); for
            // (std::vector<SelectionSingleton::SelObj>::iterator it = objs.begin(); it !=
            // objs.end(); ++it) {
            //    // build name
            //    temp = it->DocName;
            //    temp += ".";
            //    temp += it->FeatName;
            //    if (it->SubName && it->SubName[0] != '\0') {
            //        temp += ".";
            //        temp += it->SubName;
            //    }
            //    new QListWidgetItem(QString::fromLatin1(temp.c_str()), selectionView);
            //}
        }
        else if (msg.Type == Gui::SelectionChanges::SetPreselect) {
            if (strcmp(msg.pDocName, getSketchObject()->getDocument()->getName()) == 0
                && strcmp(msg.pObjectName, getSketchObject()->getNameInDocument()) == 0) {
                if (msg.pSubName) {
                    std::string shapetype(msg.pSubName);
                    if (shapetype.size() > 4 && shapetype.substr(0, 4) == "Edge") {
                        int GeoId = std::atoi(&shapetype[4]) - 1;
                        resetPreselectPoint();
                        preselection.PreselectCurve = GeoId;

                        if (sketchHandler)
                            sketchHandler->applyCursor();
                        this->updateColor();
                    }
                    else if (shapetype.size() > 6 && shapetype.substr(0, 6) == "Vertex") {
                        int PtIndex = std::atoi(&shapetype[6]) - 1;
                        setPreselectPoint(PtIndex);

                        if (sketchHandler)
                            sketchHandler->applyCursor();
                        this->updateColor();
                    }
                }
            }
        }
        else if (msg.Type == Gui::SelectionChanges::RmvPreselect) {
            resetPreselectPoint();
            if (sketchHandler)
                sketchHandler->applyCursor();
            this->updateColor();
        }
    }
}

bool ViewProviderSketch::detectAndShowPreselection(SoPickedPoint* Point, const SbVec2s& cursorPos)
{
    assert(isInEditMode());

    if (Point) {

        EditModeCoinManager::PreselectionResult result =
            editCoinManager->detectPreselection(Point, cursorPos);

        if (result.PointIndex != -1
            && result.PointIndex != preselection.PreselectPoint) {// if a new point is hit
            std::stringstream ss;
            ss << "Vertex" << result.PointIndex + 1;
            bool accepted =
                setPreselect(
                    ss.str(), Point->getPoint()[0], Point->getPoint()[1], Point->getPoint()[2])
                != 0;
            preselection.blockedPreselection = !accepted;
            if (accepted) {
                setPreselectPoint(result.PointIndex);

                if (sketchHandler)
                    sketchHandler->applyCursor();
                return true;
            }
        }
        else if (result.GeoIndex != -1
                 && result.GeoIndex != preselection.PreselectCurve) {// if a new curve is hit
            std::stringstream ss;
            if (result.GeoIndex >= 0)
                ss << "Edge" << result.GeoIndex + 1;
            else// external geometry
                ss << "ExternalEdge"
                   << -result.GeoIndex + Sketcher::GeoEnum::RefExt
                        + 1;// convert index start from -3 to 1
            bool accepted =
                setPreselect(
                    ss.str(), Point->getPoint()[0], Point->getPoint()[1], Point->getPoint()[2])
                != 0;
            preselection.blockedPreselection = !accepted;
            if (accepted) {
                resetPreselectPoint();
                preselection.PreselectCurve = result.GeoIndex;

                if (sketchHandler)
                    sketchHandler->applyCursor();
                return true;
            }
        }
        else if (result.Cross != EditModeCoinManager::PreselectionResult::Axes::None
                 && static_cast<int>(result.Cross)
                     != static_cast<int>(preselection.PreselectCross)) {// if a cross line is hit
            std::stringstream ss;
            switch (result.Cross) {
                case EditModeCoinManager::PreselectionResult::Axes::RootPoint:
                    ss << "RootPoint";
                    break;
                case EditModeCoinManager::PreselectionResult::Axes::HorizontalAxis:
                    ss << "H_Axis";
                    break;
                case EditModeCoinManager::PreselectionResult::Axes::VerticalAxis:
                    ss << "V_Axis";
                    break;
                case EditModeCoinManager::PreselectionResult::Axes::None:
                    break;// silent warning - be explicit
            }
            bool accepted =
                setPreselect(
                    ss.str(), Point->getPoint()[0], Point->getPoint()[1], Point->getPoint()[2])
                != 0;
            preselection.blockedPreselection = !accepted;
            if (accepted) {
                if (result.Cross == EditModeCoinManager::PreselectionResult::Axes::RootPoint)
                    setPreselectRootPoint();
                else
                    resetPreselectPoint();
                preselection.PreselectCross =
                    static_cast<Preselection::Axes>(static_cast<int>(result.Cross));

                if (sketchHandler)
                    sketchHandler->applyCursor();
                return true;
            }
        }
        else if (!result.ConstrIndices.empty()
                 && result.ConstrIndices
                     != preselection.PreselectConstraintSet) {// if a constraint is hit
            bool accepted = true;
            for (std::set<int>::iterator it = result.ConstrIndices.begin();
                 it != result.ConstrIndices.end();
                 ++it) {
                std::stringstream ss;
                ss << Sketcher::PropertyConstraintList::getConstraintName(*it);

                accepted &=
                    setPreselect(
                        ss.str(), Point->getPoint()[0], Point->getPoint()[1], Point->getPoint()[2])
                    != 0;

                preselection.blockedPreselection = !accepted;
                // TODO: Should we clear preselections that went through, if one fails?
            }
            if (accepted) {
                resetPreselectPoint();
                preselection.PreselectConstraintSet = result.ConstrIndices;

                if (sketchHandler)
                    sketchHandler->applyCursor();
                return true;// Preselection changed
            }
        }
        else if ((result.PointIndex == -1 && result.GeoIndex == -1
                  && result.Cross == EditModeCoinManager::PreselectionResult::Axes::None
                  && result.ConstrIndices.empty())
                 && (preselection.isPreselectPointValid() || preselection.isPreselectCurveValid()
                     || preselection.isCrossPreselected()
                     || !preselection.PreselectConstraintSet.empty()
                     || preselection.blockedPreselection)) {
            // we have just left a preselection
            resetPreselectPoint();
            preselection.blockedPreselection = false;
            if (sketchHandler)
                sketchHandler->applyCursor();
            return true;
        }
        Gui::Selection().setPreselectCoord(
            Point->getPoint()[0], Point->getPoint()[1], Point->getPoint()[2]);
    }
    else if (preselection.isPreselectCurveValid() || preselection.isPreselectPointValid()
             || !preselection.PreselectConstraintSet.empty() || preselection.isCrossPreselected()
             || preselection.blockedPreselection) {
        resetPreselectPoint();
        preselection.blockedPreselection = false;
        if (sketchHandler)
            sketchHandler->applyCursor();
        return true;
    }

    return false;
}

void ViewProviderSketch::centerSelection()
{
    Gui::MDIView* mdi = this->getActiveView();
    Gui::View3DInventor* view = qobject_cast<Gui::View3DInventor*>(mdi);
    if (!view || !isInEditMode())
        return;

    SoGroup* group = editCoinManager->getSelectedConstraints();

    Gui::View3DInventorViewer* viewer = view->getViewer();
    SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion());
    action.apply(group);
    group->unref();

    SbBox3f box = action.getBoundingBox();
    if (!box.isEmpty()) {
        SoCamera* camera = viewer->getSoRenderManager()->getCamera();
        SbVec3f direction;
        camera->orientation.getValue().multVec(SbVec3f(0, 0, 1), direction);
        SbVec3f box_cnt = box.getCenter();
        SbVec3f cam_pos = box_cnt + camera->focalDistance.getValue() * direction;
        camera->position.setValue(cam_pos);
    }
}

void ViewProviderSketch::doBoxSelection(const SbVec2s& startPos, const SbVec2s& endPos,
                                        const Gui::View3DInventorViewer* viewer)
{
    std::vector<SbVec2s> corners0;
    corners0.push_back(startPos);
    corners0.push_back(endPos);
    std::vector<SbVec2f> corners = viewer->getGLPolygon(corners0);

    // all calculations with polygon and proj are in dimensionless [0 1] screen coordinates
    Base::Polygon2d polygon;
    polygon.Add(Base::Vector2d(corners[0].getValue()[0], corners[0].getValue()[1]));
    polygon.Add(Base::Vector2d(corners[0].getValue()[0], corners[1].getValue()[1]));
    polygon.Add(Base::Vector2d(corners[1].getValue()[0], corners[1].getValue()[1]));
    polygon.Add(Base::Vector2d(corners[1].getValue()[0], corners[0].getValue()[1]));

    Gui::ViewVolumeProjection proj(viewer->getSoRenderManager()->getCamera()->getViewVolume());

    Sketcher::SketchObject* sketchObject = getSketchObject();

    Base::Placement Plm = getEditingPlacement();

    int intGeoCount = sketchObject->getHighestCurveIndex() + 1;
    int extGeoCount = sketchObject->getExternalGeometryCount();

    const std::vector<Part::Geometry*> geomlist =
        sketchObject->getCompleteGeometry();// without memory allocation
    assert(int(geomlist.size()) == extGeoCount + intGeoCount);
    assert(int(geomlist.size()) >= 2);

    auto inBBCoords = [&Plm, &proj](const Base::Vector3d & point) {
        Base::Vector3d pnt;
        Plm.multVec(point, pnt);
        return proj(pnt);
    };

    int VertexId = -1; // the loop below should be in sync with the main loop in
                       // ViewProviderSketch::draw so that the vertex indices are calculated
                       // correctly
    int GeoId = 0;

    bool touchMode = false;
    // check if selection goes from the right to the left side (for touch-selection where even
    // partially boxed objects get selected)
    if (corners[0].getValue()[0] > corners[1].getValue()[0])
        touchMode = true;

    auto selectVertex = [this](int vertexid) {
        std::stringstream ss;
        ss << "Vertex" << vertexid;
        addSelection2(ss.str());
    };

    auto selectEdge = [this](int edgeid) {
        std::stringstream ss;
        ss << "Edge" << edgeid;
        addSelection2(ss.str());
    };

    auto selectVertexIfInsideBox = [&polygon, &VertexId, &selectVertex](const Base::Vector3d & point) {
        if (polygon.Contains(Base::Vector2d(point.x, point.y))) {
            selectVertex( VertexId + 1);
            return true; // inside
        }

        return false; // outside
    };

    auto selectEdgeIfInsideBox = [&touchMode, &polygon, &GeoId, &inBBCoords, &selectEdge,
                                  numSegments = viewProviderParameters.stdCountSegments](auto geo){

        if constexpr (std::is_same<decltype(geo), Part::GeomBSplineCurve>::value) {
            numSegments *= geo->countKnots();  // one less segments than knots
        }

        double segment = (geo->getLastParameter() - geo->getFirstParameter()) / numSegments;

        bool bpolyInside = true;

        for (int i = 0; i < numSegments; i++) {
            Base::Vector3d pnt = geo->value(geo->getFirstParameter() + i * segment);
            pnt = inBBCoords(pnt);
            if (!polygon.Contains(Base::Vector2d(pnt.x, pnt.y))) {
                    bpolyInside = false;
                    if (!touchMode) {
                        break;
                    }
                }
                else if (touchMode) {
                    bpolyInside = true;
                    break;
            }
        }

        if (bpolyInside) {
            selectEdge(GeoId+1);
        }
    };

    for (std::vector<Part::Geometry*>::const_iterator it = geomlist.begin();
         it != geomlist.end() - 2;
         ++it, ++GeoId) {

        if (GeoId >= intGeoCount)
            GeoId = -extGeoCount;

        if ((*it)->is<Part::GeomPoint>()) {
            // ----- Check if single point lies inside box selection -----/
            const Part::GeomPoint* point = static_cast<const Part::GeomPoint*>(*it);
            Base::Vector3d pnt0 = inBBCoords(point->getPoint());
            VertexId++;

            selectVertexIfInsideBox(pnt0);
        }
        else if ((*it)->is<Part::GeomLineSegment>()) {
            // ----- Check if line segment lies inside box selection -----/
            const Part::GeomLineSegment* lineSeg = static_cast<const Part::GeomLineSegment*>(*it);
            Base::Vector3d pnt1 = inBBCoords(lineSeg->getStartPoint());
            Base::Vector3d pnt2 = inBBCoords(lineSeg->getEndPoint());

            VertexId++;
            bool pnt1Inside = selectVertexIfInsideBox(pnt1);

            VertexId++;
            bool pnt2Inside = selectVertexIfInsideBox(pnt2);
            polygon.Contains(Base::Vector2d(pnt1.x, pnt1.y));
            polygon.Contains(Base::Vector2d(pnt2.x, pnt2.y));


            if ((pnt1Inside && pnt2Inside) && !touchMode) {
                selectEdge(GeoId+1);
            }
            // check if line intersects with polygon
            else if (touchMode) {
                Base::Polygon2d lineAsPolygon;
                lineAsPolygon.Add(Base::Vector2d(pnt1.x, pnt1.y));
                lineAsPolygon.Add(Base::Vector2d(pnt2.x, pnt2.y));
                std::list<Base::Polygon2d> resultList;
                polygon.Intersect(lineAsPolygon, resultList);
                if (!resultList.empty()) {
                    selectEdge(GeoId+1);
                }
            }
        }
        else if ((*it)->isDerivedFrom<Part::GeomConic>()) {
            // ----- Check if circle lies inside box selection -----/
            /// TODO: Make it impossible to miss the conic if it's big and the selection pretty
            /// thin.
            const Part::GeomConic* circle = static_cast<const Part::GeomConic*>(*it);
            Base::Vector3d pnt0 = inBBCoords(circle->getCenter());
            VertexId++;

            bool pnt0Inside = selectVertexIfInsideBox(pnt0);

            if (pnt0Inside || touchMode) {
                selectEdgeIfInsideBox(circle);
            }
        }
        else if ((*it)->isDerivedFrom<Part::GeomArcOfConic>()) {
            // Check if arc lies inside box selection
            const Part::GeomArcOfConic* aoc = static_cast<const Part::GeomArcOfConic*>(*it);

            Base::Vector3d pnt0 = inBBCoords(aoc->getStartPoint(/*emulateCCW=*/true));
            VertexId++;
            bool pnt0Inside = selectVertexIfInsideBox(pnt0);

            Base::Vector3d pnt1 = inBBCoords(aoc->getEndPoint(/*emulateCCW=*/true));
            VertexId++;
            bool pnt1Inside = selectVertexIfInsideBox(pnt1);

            Base::Vector3d pnt2 = inBBCoords(aoc->getCenter());
            VertexId++;
            selectVertexIfInsideBox(pnt2);

            if ((pnt0Inside && pnt1Inside) || touchMode) {
                selectEdgeIfInsideBox(aoc);
            }
        }
        else if ((*it)->is<Part::GeomBSplineCurve>()) {
            const Part::GeomBSplineCurve* spline = static_cast<const Part::GeomBSplineCurve*>(*it);

            Base::Vector3d pnt1 = inBBCoords(spline->getStartPoint());
            VertexId++;
            bool pnt1Inside = selectVertexIfInsideBox(pnt1);

            Base::Vector3d pnt2 = inBBCoords(spline->getEndPoint());
            VertexId++;
            bool pnt2Inside = selectVertexIfInsideBox(pnt2);

            if ((pnt1Inside && pnt2Inside) || touchMode) {
                selectEdgeIfInsideBox(spline);
            }
        }
        else {
            Base::Console().DeveloperError("ViewProviderSketch::doBoxSelection",
                                           "Geometry type is unsupported. Selection may be unsynchronised and fail.");
        }
    }

    Base::Vector3d pnt0 = proj(Plm.getPosition());
    if (polygon.Contains(Base::Vector2d(pnt0.x, pnt0.y))) {
        std::stringstream ss;
        ss << "RootPoint";
        addSelection2(ss.str());
    }
}

void ViewProviderSketch::updateColor()
{
    assert(isInEditMode());

    editCoinManager->updateColor();
}

bool ViewProviderSketch::doubleClicked()
{
    Gui::Application::Instance->activeDocument()->setEdit(this);
    return true;
}

float ViewProviderSketch::getScaleFactor() const
{
    assert(isInEditMode());
    Gui::MDIView* mdi =
        Gui::Application::Instance->editViewOfNode(editCoinManager->getRootEditNode());
    if (mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) {
        Gui::View3DInventorViewer* viewer = static_cast<Gui::View3DInventor*>(mdi)->getViewer();
        SoCamera* camera = viewer->getSoRenderManager()->getCamera();
        float scale = camera->getViewVolume(camera->aspectRatio.getValue())
                          .getWorldToScreenScale(SbVec3f(0.f, 0.f, 0.f), 0.1f)
            / 3;
        return scale;
    }
    else {
        return 1.f;
    }
}

// This function ensures that the geometry used for drawing takes into account:
// 1. the OCC mandated weight, which is normalised for non-rational BSplines, but not normalised for
// rational BSplines. That includes properly sizing for drawing any weight constraint. This function
// ensures that both the geometry of the SketchObject and solver are updated with the new value of
// the scaling factor (via the extension)
// 2. the scaling factor, including inserting the scaling factor into the
// ViewProviderSketchGeometryExtension so as to enable That ensures that dragging operations on the
// circles of the poles of the B-Splines are properly rendered.
//
// This function takes a reference to a vector of deep copies to delete. These deep copies are
// necessary to transparently perform (1) while doing (2).
void ViewProviderSketch::scaleBSplinePoleCirclesAndUpdateSolverAndSketchObjectGeometry(
    GeoListFacade& geolistfacade, bool geometrywithmemoryallocation)
{
    // In order to allow to tweak geometry and insert scaling factors, this function needs to
    // change the geometry vector. This is highly exceptional for a drawing function and special
    // care needs to be taken. This is valid because:
    // 1. The treatment is exceptional and no other appropriate place is available to perform this
    // tweak
    // 2. The original object needs to remain const for the benefit of all other class hierarchy of
    // drawing functions
    // 3. When referring to actual geometry, the modified pointers are short lived, as they are
    // destroyed after drawing
    auto& tempGeo = geolistfacade.geomlist;

    int GeoId = 0;
    for (auto it = tempGeo.begin(); it != tempGeo.end() - 2; ++it, GeoId++) {
        if (GeoId >= geolistfacade.getInternalCount())
            GeoId = -geolistfacade.getExternalCount();

        if ((*it)->getGeometry()->is<Part::GeomCircle>()) {// circle
            const Part::GeomCircle* circle =
                static_cast<const Part::GeomCircle*>((*it)->getGeometry());
            auto& gf = (*it);

            // BSpline weights have a radius corresponding to the weight value
            // However, in order for them proportional to the B-Spline size,
            // the scenograph has a size scalefactor times the weight
            //
            // This code produces the scaled up version of the geometry for the scenograph
            if (gf->getInternalType() == InternalType::BSplineControlPoint) {
                for (auto c : getSketchObject()->Constraints.getValues()) {
                    if (c->Type == InternalAlignment && c->AlignmentType == BSplineControlPoint
                        && c->First == GeoId) {
                        auto bspline = dynamic_cast<const Part::GeomBSplineCurve*>(
                            tempGeo[c->Second]->getGeometry());

                        if (bspline) {
                            auto weights = bspline->getWeights();

                            double weight = 1.0;
                            if (c->InternalAlignmentIndex < int(weights.size()))
                                weight = weights[c->InternalAlignmentIndex];

                            // tentative scaling factor:
                            // proportional to the length of the bspline
                            // inversely proportional to the number of poles
                            double scalefactor = bspline->length(bspline->getFirstParameter(),
                                                                 bspline->getLastParameter())
                                / 10.0 / weights.size();

                            double vradius = weight * scalefactor;
                            if (!bspline->isRational()) {
                                // OCCT sets the weights to 1.0 if a bspline is non-rational, but if
                                // the user has a weight constraint on any pole it would cause a
                                // visual artifact of having a constraint with a different radius
                                // and an unscaled circle so better scale the circles.
                                std::vector<int> polegeoids;
                                polegeoids.reserve(weights.size());

                                for (auto ic : getSketchObject()->Constraints.getValues()) {
                                    if (ic->Type == InternalAlignment
                                        && ic->AlignmentType == BSplineControlPoint
                                        && ic->Second == c->Second) {
                                        polegeoids.push_back(ic->First);
                                    }
                                }

                                for (auto ic : getSketchObject()->Constraints.getValues()) {
                                    if (ic->Type == Weight) {
                                        auto pos = std::find(
                                            polegeoids.begin(), polegeoids.end(), ic->First);

                                        if (pos != polegeoids.end()) {
                                            vradius = ic->getValue() * scalefactor;
                                            break;// one is enough, otherwise it would not be
                                                  // non-rational
                                        }
                                    }
                                }
                            }

                            Part::GeomCircle* tmpcircle;

                            if (geometrywithmemoryallocation) {// with memory allocation
                                tmpcircle = const_cast<Part::GeomCircle*>(circle);
                                tmpcircle->setRadius(vradius);
                            }
                            else {// without memory allocation
                                tmpcircle = static_cast<Part::GeomCircle*>(circle->clone());
                                tmpcircle->setRadius(vradius);
                                tempGeo[GeoId] = GeometryFacade::getFacade(
                                    tmpcircle, true);// this is the circle that will be drawn, with
                                                     // the updated vradius, the facade takes
                                                     // ownership and will deallocate.
                            }

                            if (!circle->hasExtension(
                                    SketcherGui::ViewProviderSketchGeometryExtension::
                                        getClassTypeId())) {
                                // It is ok to add this kind of extension to a const geometry
                                // because:
                                // 1. It does not modify the object in a way that affects property
                                // state, just ViewProvider representation
                                // 2. If it is lost (for example upon undo), redrawing will
                                // reinstate it with the correct value
                                const_cast<Part::GeomCircle*>(circle)->setExtension(
                                    std::make_unique<
                                        SketcherGui::ViewProviderSketchGeometryExtension>());
                            }

                            auto vpext = std::const_pointer_cast<
                                SketcherGui::ViewProviderSketchGeometryExtension>(
                                std::static_pointer_cast<
                                    const SketcherGui::ViewProviderSketchGeometryExtension>(
                                    circle
                                        ->getExtension(
                                            SketcherGui::ViewProviderSketchGeometryExtension::
                                                getClassTypeId())
                                        .lock()));

                            vpext->setRepresentationFactor(scalefactor);

                            // save scale factor for any prospective dragging operation
                            // 1. Solver must be updated, in case a dragging operation starts
                            // 2. if temp geometry is being used (with memory allocation), then the
                            // copy we have here must be updated. If
                            //    no temp geometry is being used, then the normal geometry must be
                            //    updated.
                            // make solver be ready for a dragging operation
                            auto solverext = vpext->copy();

                            getSketchObject()->updateSolverExtension(GeoId, std::move(solverext));
                        }
                        break;
                    }
                }
            }
        }
    }
}

void ViewProviderSketch::draw(bool temp /*=false*/, bool rebuildinformationoverlay /*=true*/)
{
    assert(isInEditMode());

    // ============== Retrieve geometry to be represented =================================

    auto geolistfacade = temp ? getSolvedSketch().extractGeoListFacade() :// with memory allocation
        getSketchObject()->getGeoListFacade();// without memory allocation

    assert(int(geolistfacade.geomlist.size()) >= 2);

    // ============== Prepare geometry for representation ==================================

    // ************ Manage BSpline pole circle scaling  ****************************

    // This function ensures that the geometry used for drawing takes into account:
    // 1. the OCC mandated weight, which is normalised for non-rational BSplines, but not normalised
    // for rational BSplines. That includes properly sizing for drawing any weight constraint. This
    // function ensures that both the geometry of the SketchObject and solver are updated with the
    // new value of the scaling factor (via the extension)
    // 2. the scaling factor, including inserting the scaling factor into the
    // ViewProviderSketchGeometryExtension so as to enable That ensures that dragging operations on
    // the circles of the poles of the B-Splines are properly rendered.
    //
    // This function takes a reference to a vector of deep copies to delete. These deep copies are
    // necessary to transparently perform (1) while doing (2).

    scaleBSplinePoleCirclesAndUpdateSolverAndSketchObjectGeometry(geolistfacade, temp);

    // ============== Render geometry, constraints and geometry information overlays
    // ==================================

    editCoinManager->processGeometryConstraintsInformationOverlay(geolistfacade,
                                                                  rebuildinformationoverlay);

    // Avoids unneeded calls to pixmapFromSvg
    if (Mode == STATUS_NONE || Mode == STATUS_SKETCH_UseHandler) {
        editCoinManager->drawConstraintIcons(geolistfacade);
        editCoinManager->updateColor(geolistfacade);
    }

    Gui::MDIView* mdi = this->getActiveView();
    if (mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) {
        static_cast<Gui::View3DInventor*>(mdi)->getViewer()->redraw();
    }
}

void ViewProviderSketch::setIsShownVirtualSpace(bool isshownvirtualspace)
{
    viewProviderParameters.isShownVirtualSpace = isshownvirtualspace;

    editCoinManager->updateVirtualSpace();

    signalConstraintsChanged();
}

bool ViewProviderSketch::getIsShownVirtualSpace() const
{
    return viewProviderParameters.isShownVirtualSpace;
}


void ViewProviderSketch::drawEdit(const std::vector<Base::Vector2d>& EditCurve)
{
    editCoinManager->drawEdit(EditCurve);
}

void ViewProviderSketch::drawEdit(const std::list<std::vector<Base::Vector2d>>& list)
{
    editCoinManager->drawEdit(list);
}

void ViewProviderSketch::drawEditMarkers(const std::vector<Base::Vector2d>& EditMarkers,
                                         unsigned int augmentationlevel)
{
    editCoinManager->drawEditMarkers(EditMarkers, augmentationlevel);
}

void ViewProviderSketch::updateData(const App::Property* prop)
{
    ViewProvider2DObject::updateData(prop);

    // In the case of an undo/redo transaction, updateData is triggered by
    // SketchObject::onUndoRedoFinished() in the solve() In the case of an internal transaction,
    // touching the geometry results in a call to updateData.
    if (isInEditMode() && !getSketchObject()->getDocument()->isPerformingTransaction()
        && !getSketchObject()->isPerformingInternalTransaction()
        && (prop == &(getSketchObject()->Geometry) || prop == &(getSketchObject()->Constraints))) {

        // At this point, we do not need to solve the Sketch
        // If we are adding geometry an update can be triggered before the sketch is actually
        // solved. Because a solve is mandatory to any addition (at least to update the DoF of the
        // solver), only when the solver geometry is the same in number than the sketch geometry an
        // update should trigger a redraw. This reduces even more the number of redraws per
        // insertion of geometry

        // solver information is also updated when no matching geometry, so that if a solving fails
        // this failed solving info is presented to the user
        UpdateSolverInformation();// just update the solver window with the last SketchObject
                                  // solving information

        if (getSketchObject()->getExternalGeometryCount()
                + getSketchObject()->getHighestCurveIndex() + 1
            == getSolvedSketch().getGeometrySize()) {
            Gui::MDIView* mdi = Gui::Application::Instance->editDocument()->getActiveView();
            if (mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId()))
                draw(false, true);

            signalConstraintsChanged();
        }

        if (prop != &getSketchObject()->Constraints)
            signalElementsChanged();
    }
}

void ViewProviderSketch::onChanged(const App::Property* prop)
{
    if (prop == &VisualLayerList) {
        if (isInEditMode()) {
            // Configure and rebuild Coin SceneGraph
            editCoinManager->updateGeometryLayersConfiguration();
        }
        return;
    }
    // call father
    ViewProviderPart::onChanged(prop);
}

void ViewProviderSketch::attach(App::DocumentObject* pcFeat)
{
    ViewProviderPart::attach(pcFeat);
}

void ViewProviderSketch::setupContextMenu(QMenu* menu, QObject* receiver, const char* member)
{
    menu->addAction(tr("Edit sketch"), receiver, member);
    // Call the extensions
    Gui::ViewProvider::setupContextMenu(menu, receiver, member);
}

bool ViewProviderSketch::setEdit(int ModNum)
{
    Q_UNUSED(ModNum)
    // When double-clicking on the item for this sketch the
    // object unsets and sets its edit mode without closing
    // the task panel
    Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
    TaskDlgEditSketch* sketchDlg = qobject_cast<TaskDlgEditSketch*>(dlg);
    if (sketchDlg && sketchDlg->getSketchView() != this)
        sketchDlg = nullptr;// another sketch left open its task panel
    if (dlg && !sketchDlg) {
        QMessageBox msgBox;
        msgBox.setText(tr("A dialog is already open in the task panel"));
        msgBox.setInformativeText(tr("Do you want to close this dialog?"));
        msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        msgBox.setDefaultButton(QMessageBox::Yes);
        int ret = msgBox.exec();
        if (ret == QMessageBox::Yes)
            Gui::Control().closeDialog();
        else
            return false;
    }

    Sketcher::SketchObject* sketch = getSketchObject();
    if (!sketch->evaluateConstraints()) {
        QMessageBox box(Gui::getMainWindow());
        box.setIcon(QMessageBox::Critical);
        box.setWindowTitle(tr("Invalid sketch"));
        box.setText(tr("Do you want to open the sketch validation tool?"));
        box.setInformativeText(tr("The sketch is invalid and cannot be edited."));
        box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        box.setDefaultButton(QMessageBox::Yes);
        switch (box.exec()) {
            case QMessageBox::Yes:
                Gui::Control().showDialog(new TaskSketcherValidation(getSketchObject()));
                break;
            default:
                break;
        }
        return false;
    }

    // clear the selection (convenience)
    Gui::Selection().clearSelection();
    Gui::Selection().rmvPreselect();

    this->attachSelection();

    auto gridnode = getGridNode();
    Base::Placement plm = getEditingPlacement();
    setGridOrientation(plm.getPosition(), plm.getRotation());
    addNodeToRoot(gridnode);
    setGridEnabled(true);

    // create the container for the additional edit data
    assert(!isInEditMode());
    preselection.reset();
    selection.reset();
    editCoinManager = std::make_unique<EditModeCoinManager>(*this);
    snapManager = std::make_unique<SnapManager>(*this);

    auto editDoc = Gui::Application::Instance->editDocument();
    App::DocumentObject* editObj = getSketchObject();
    std::string editSubName;
    ViewProviderDocumentObject* editVp = nullptr;
    if (editDoc) {
        editDoc->getInEdit(&editVp, &editSubName);
        if (editVp)
            editObj = editVp->getObject();
    }

    // visibility automation
    try {
        Gui::Command::addModule(Gui::Command::Gui, "Show");
        try {
            QString cmdstr =
                QString::fromLatin1(
                    "ActiveSketch = App.getDocument('%1').getObject('%2')\n"
                    "tv = Show.TempoVis(App.ActiveDocument, tag= ActiveSketch.ViewObject.TypeId)\n"
                    "ActiveSketch.ViewObject.TempoVis = tv\n"
                    "if ActiveSketch.ViewObject.EditingWorkbench:\n"
                    "  tv.activateWorkbench(ActiveSketch.ViewObject.EditingWorkbench)\n"
                    "if ActiveSketch.ViewObject.HideDependent:\n"
                    "  tv.hide(tv.get_all_dependent(%3, '%4'))\n"
                    "if ActiveSketch.ViewObject.ShowSupport:\n"
                    "  tv.show([ref[0] for ref in ActiveSketch.AttachmentSupport if not "
                    "ref[0].isDerivedFrom(\"PartDesign::Plane\")])\n"
                    "if ActiveSketch.ViewObject.ShowLinks:\n"
                    "  tv.show([ref[0] for ref in ActiveSketch.ExternalGeometry])\n"
                    "tv.sketchClipPlane(ActiveSketch, ActiveSketch.ViewObject.SectionView)\n"
                    "tv.hide(ActiveSketch)\n"
                    "del(tv)\n"
                    "del(ActiveSketch)\n")
                    .arg(QString::fromLatin1(getDocument()->getDocument()->getName()),
                         QString::fromLatin1(getSketchObject()->getNameInDocument()),
                         QString::fromLatin1(Gui::Command::getObjectCmd(editObj).c_str()),
                         QString::fromLatin1(editSubName.c_str()));
            QByteArray cmdstr_bytearray = cmdstr.toLatin1();
            Gui::Command::runCommand(Gui::Command::Gui, cmdstr_bytearray);
        }
        catch (Base::PyException& e) {
            Base::Console().DeveloperError(
                "ViewProviderSketch", "setEdit: visibility automation failed with an error: \n");
            e.ReportException();
        }
    }
    catch (Base::PyException&) {
        Base::Console().DeveloperWarning(
            "ViewProviderSketch",
            "setEdit: could not import Show module. Visibility automation will not work.\n");
    }

    // start the edit dialog
    if (!sketchDlg)
        sketchDlg = new TaskDlgEditSketch(this);

    connectionToolWidget = sketchDlg->registerToolWidgetChanged(std::bind(&SketcherGui::ViewProviderSketch::slotToolWidgetChanged, this, sp::_1));

    Gui::Control().showDialog(sketchDlg);

    // This call to the solver is needed to initialize the DoF and solve time controls
    // The false parameter indicates that the geometry of the SketchObject shall not be updateData
    // so as not to trigger an onChanged that would set the document as modified and trigger a
    // recompute if we just close the sketch without touching anything.
    if (getSketchObject()->AttachmentSupport.getValue()) {
        if (!getSketchObject()->evaluateSupport())
            getSketchObject()->validateExternalLinks();
    }

    // There are geometry extensions introduced by the solver and geometry extensions introduced by
    // the viewprovider.
    // 1. It is important that the solver has geometry with updated extensions.
    // 2. It is important that the viewprovider has up-to-date solver information
    //
    // The decision is to maintain the "first solve then draw" order, which is consistent with the
    // rest of the Sketcher for example in geometry creation. Then, the ViewProvider is responsible
    // for updating the solver geometry when appropriate, as it is the ViewProvider that is
    // introducing its geometry extensions.
    //
    // In order to have updated solver information, solve must take "true", this cause the Geometry
    // property to be updated with the solver information, including solver extensions, and triggers
    // a draw(true) via ViewProvider::UpdateData.
    getSketchObject()->solve(true);

    //NOLINTBEGIN
    connectUndoDocument = getDocument()->signalUndoDocument.connect(
        std::bind(&ViewProviderSketch::slotUndoDocument, this, sp::_1));
    connectRedoDocument = getDocument()->signalRedoDocument.connect(
        std::bind(&ViewProviderSketch::slotRedoDocument, this, sp::_1));
    //NOLINTEND

    // Enable solver initial solution update while dragging.
    getSketchObject()->setRecalculateInitialSolutionWhileMovingPoint(
        viewProviderParameters.recalculateInitialSolutionWhileDragging);

    // intercept del key press from main app
    listener = new ShortcutListener(this);

    Gui::getMainWindow()->installEventFilter(listener);

    Workbench::enterEditMode();

    return true;
}

QString ViewProviderSketch::appendConflictMsg(const std::vector<int>& conflicting)
{
    return appendConstraintMsg(tr("Please remove the following constraint:"),
                               tr("Please remove at least one of the following constraints:"),
                               conflicting);
}

QString ViewProviderSketch::appendRedundantMsg(const std::vector<int>& redundant)
{
    return appendConstraintMsg(tr("Please remove the following redundant constraint:"),
                               tr("Please remove the following redundant constraints:"),
                               redundant);
}

QString ViewProviderSketch::appendPartiallyRedundantMsg(const std::vector<int>& partiallyredundant)
{
    return appendConstraintMsg(tr("The following constraint is partially redundant:"),
                               tr("The following constraints are partially redundant:"),
                               partiallyredundant);
}

QString ViewProviderSketch::appendMalformedMsg(const std::vector<int>& malformed)
{
    return appendConstraintMsg(tr("Please remove the following malformed constraint:"),
                               tr("Please remove the following malformed constraints:"),
                               malformed);
}

QString ViewProviderSketch::appendConstraintMsg(const QString& singularmsg,
                                                const QString& pluralmsg,
                                                const std::vector<int>& vector)
{
    QString msg;
    QTextStream ss(&msg);
    if (!vector.empty()) {
        if (vector.size() == 1)
            ss << singularmsg;
        else
            ss << pluralmsg;
        ss << "\n";
        ss << vector[0];
        for (unsigned int i = 1; i < vector.size(); i++)
            ss << ", " << vector[i];

        ss << "\n";
    }
    return msg;
}

inline QString intListHelper(const std::vector<int>& ints)
{
    QString results;
    if (ints.size() < 8) {// The 8 is a bit heuristic... more than that and we shift formats
        for (const auto i : ints) {
            if (results.isEmpty())
                results.append(QString::fromUtf8("%1").arg(i));
            else
                results.append(QString::fromUtf8(", %1").arg(i));
        }
    }
    else {
        const int numToShow = 3;
        int more = ints.size() - numToShow;
        for (int i = 0; i < numToShow; ++i) {
            results.append(QString::fromUtf8("%1, ").arg(ints[i]));
        }
        results.append(QCoreApplication::translate("ViewProviderSketch", "and %1 more").arg(more));
    }
    std::string testString = results.toStdString();
    return results;
}

void ViewProviderSketch::UpdateSolverInformation()
{
    // Updates Solver Information with the Last solver execution at SketchObject level
    int dofs = getSketchObject()->getLastDoF();
    bool hasConflicts = getSketchObject()->getLastHasConflicts();
    bool hasRedundancies = getSketchObject()->getLastHasRedundancies();
    bool hasPartiallyRedundant = getSketchObject()->getLastHasPartialRedundancies();
    bool hasMalformed = getSketchObject()->getLastHasMalformedConstraints();

    if (getSketchObject()->Geometry.getSize() == 0) {
        signalSetUp(QString::fromUtf8("empty_sketch"), tr("Empty sketch"), QString(), QString());
    }
    else if (dofs < 0 || hasConflicts) {// over-constrained sketch
        signalSetUp(
            QString::fromUtf8("conflicting_constraints"),
            tr("Over-constrained:") + QLatin1String(" "),
            QString::fromUtf8("#conflicting"),
            QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastConflicting())));
    }
    else if (hasMalformed) {// malformed constraints
        signalSetUp(QString::fromUtf8("malformed_constraints"),
                    tr("Malformed constraints:") + QLatin1String(" "),
                    QString::fromUtf8("#malformed"),
                    QString::fromUtf8("(%1)").arg(
                        intListHelper(getSketchObject()->getLastMalformedConstraints())));
    }
    else if (hasRedundancies) {
        signalSetUp(
            QString::fromUtf8("redundant_constraints"),
            tr("Redundant constraints:") + QLatin1String(" "),
            QString::fromUtf8("#redundant"),
            QString::fromUtf8("(%1)").arg(intListHelper(getSketchObject()->getLastRedundant())));
    }
    else if (hasPartiallyRedundant) {
        signalSetUp(QString::fromUtf8("partially_redundant_constraints"),
                    tr("Partially redundant:") + QLatin1String(" "),
                    QString::fromUtf8("#partiallyredundant"),
                    QString::fromUtf8("(%1)").arg(
                        intListHelper(getSketchObject()->getLastPartiallyRedundant())));
    }
    else if (getSketchObject()->getLastSolverStatus() != 0) {
        signalSetUp(QString::fromUtf8("solver_failed"),
                    tr("Solver failed to converge"),
                    QString::fromUtf8(""),
                    QString::fromUtf8(""));
    }
    else if (dofs > 0) {
        signalSetUp(QString::fromUtf8("under_constrained"),
                    tr("Under constrained:") + QLatin1String(" "),
                    QString::fromUtf8("#dofs"),
                    tr("%n DoF(s)", "", dofs));
    }
    else {
        signalSetUp(
            QString::fromUtf8("fully_constrained"), tr("Fully constrained"), QString(), QString());
    }
}

void ViewProviderSketch::unsetEdit(int ModNum)
{
    Q_UNUSED(ModNum);

    setGridEnabled(false);
    auto gridnode = getGridNode();
    pcRoot->removeChild(gridnode);

    Workbench::leaveEditMode();

    if (listener) {
        Gui::getMainWindow()->removeEventFilter(listener);
        delete listener;
    }

    if (isInEditMode()) {
        if (sketchHandler)
            deactivateHandler();

        editCoinManager = nullptr;
        snapManager = nullptr;
        preselection.reset();
        selection.reset();
        this->detachSelection();

        App::AutoTransaction trans("Sketch recompute");
        try {
            // and update the sketch
            // getSketchObject()->getDocument()->recompute();
            Gui::Command::updateActive();
        }
        catch (...) {
        }
    }

    // clear the selection and set the new/edited sketch(convenience)
    Gui::Selection().clearSelection();
    Gui::Selection().addSelection(editDocName.c_str(), editObjName.c_str(), editSubName.c_str());

    connectUndoDocument.disconnect();
    connectRedoDocument.disconnect();

    // when pressing ESC make sure to close the dialog
    Gui::Control().closeDialog();

    // visibility automation
    try {
        QString cmdstr =
            QString::fromLatin1("ActiveSketch = App.getDocument('%1').getObject('%2')\n"
                                "tv = ActiveSketch.ViewObject.TempoVis\n"
                                "if tv:\n"
                                "  tv.restore()\n"
                                "ActiveSketch.ViewObject.TempoVis = None\n"
                                "del(tv)\n"
                                "del(ActiveSketch)\n")
                .arg(QString::fromLatin1(getDocument()->getDocument()->getName()),
                     QString::fromLatin1(getSketchObject()->getNameInDocument()));
        QByteArray cmdstr_bytearray = cmdstr.toLatin1();
        Gui::Command::runCommand(Gui::Command::Gui, cmdstr_bytearray);
    }
    catch (Base::PyException& e) {
        Base::Console().DeveloperError(
            "ViewProviderSketch",
            "unsetEdit: visibility automation failed with an error: %s \n",
            e.what());
    }
}

void ViewProviderSketch::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum)
{
    Q_UNUSED(ModNum);
    // visibility automation: save camera
    if (!this->TempoVis.getValue().isNone()) {
        try {
            QString cmdstr =
                QString::fromLatin1(
                    "ActiveSketch = App.getDocument('%1').getObject('%2')\n"
                    "if ActiveSketch.ViewObject.RestoreCamera:\n"
                    "  ActiveSketch.ViewObject.TempoVis.saveCamera()\n"
                    "  if ActiveSketch.ViewObject.ForceOrtho:\n"
                    "    "
                    "ActiveSketch.ViewObject.Document.ActiveView.setCameraType('Orthographic')\n")
                    .arg(QString::fromLatin1(getDocument()->getDocument()->getName()),
                         QString::fromLatin1(getSketchObject()->getNameInDocument()));
            QByteArray cmdstr_bytearray = cmdstr.toLatin1();
            Gui::Command::runCommand(Gui::Command::Gui, cmdstr_bytearray);
        }
        catch (Base::PyException& e) {
            Base::Console().DeveloperError(
                "ViewProviderSketch",
                "setEdit: visibility automation failed with an error: %s \n",
                e.what());
        }
    }

    auto editDoc = Gui::Application::Instance->editDocument();
    editDocName.clear();
    if (editDoc) {
        ViewProviderDocumentObject* parent = nullptr;
        editDoc->getInEdit(&parent, &editSubName);
        if (parent) {
            editDocName = editDoc->getDocument()->getName();
            editObjName = parent->getObject()->getNameInDocument();
        }
    }
    if (editDocName.empty()) {
        editDocName = getObject()->getDocument()->getName();
        editObjName = getObject()->getNameInDocument();
        editSubName.clear();
    }
    const char* dot = strrchr(editSubName.c_str(), '.');
    if (!dot)
        editSubName.clear();
    else
        editSubName.resize(dot - editSubName.c_str() + 1);

    Base::Placement plm = getEditingPlacement();
    Base::Rotation tmp(plm.getRotation());

    SbRotation rot((float)tmp[0], (float)tmp[1], (float)tmp[2], (float)tmp[3]);

    // Will the sketch be visible from the new position (#0000957)?
    //
    SoCamera* camera = viewer->getSoRenderManager()->getCamera();
    SbVec3f curdir;// current view direction
    camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), curdir);
    SbVec3f focal = camera->position.getValue() + camera->focalDistance.getValue() * curdir;

    SbVec3f newdir;// future view direction
    rot.multVec(SbVec3f(0, 0, -1), newdir);
    SbVec3f newpos = focal - camera->focalDistance.getValue() * newdir;

    SbVec3f plnpos = Base::convertTo<SbVec3f>(plm.getPosition());
    double dist = (plnpos - newpos).dot(newdir);
    if (dist < 0) {
        float focalLength = camera->focalDistance.getValue() - dist + 5;
        camera->position = focal - focalLength * curdir;
        camera->focalDistance.setValue(focalLength);
    }

    viewer->setCameraOrientation(rot);

    viewer->setEditing(true);
    viewer->setSelectionEnabled(false);

    viewer->addGraphicsItem(rubberband.get());
    rubberband->setViewer(viewer);

    viewer->setupEditingRoot();

    cameraSensor.setData(new VPRender {this, viewer->getSoRenderManager()});
    cameraSensor.attach(viewer->getSoRenderManager()->getSceneGraph());
}

void ViewProviderSketch::unsetEditViewer(Gui::View3DInventorViewer* viewer)
{
    auto dataPtr = static_cast<VPRender*>(cameraSensor.getData());
    delete dataPtr;
    cameraSensor.setData(nullptr);
    cameraSensor.detach();

    viewer->removeGraphicsItem(rubberband.get());
    viewer->setEditing(false);
    viewer->setSelectionEnabled(true);
}

void ViewProviderSketch::camSensCB(void* data, SoSensor*)
{
    VPRender* proxyVPrdr = static_cast<VPRender*>(data);
    if (!proxyVPrdr)
        return;

    auto vp = proxyVPrdr->vp;
    auto cam = proxyVPrdr->renderMgr->getCamera();

    if (cam == nullptr)
        Base::Console().DeveloperWarning("ViewProviderSketch", "Camera is nullptr!\n");
    else
        vp->onCameraChanged(cam);
}

void ViewProviderSketch::onCameraChanged(SoCamera* cam)
{
    auto rotSk = Base::Rotation(getDocument()->getEditingTransform());// sketch orientation
    auto rotc = cam->orientation.getValue().getValue();
    auto rotCam =
        Base::Rotation(rotc[0],
                       rotc[1],
                       rotc[2],
                       rotc[3]);// camera orientation (needed because float to double conversion)

    // Is camera in the same hemisphere as positive sketch normal ?
    auto orientation = (rotCam.invert() * rotSk).multVec(Base::Vector3d(0, 0, 1));
    auto tmpFactor = orientation.z < 0 ? -1 : 1;

    if (tmpFactor != viewOrientationFactor) {// redraw only if viewing side changed
        Base::Console().Log("Switching side, now %s, redrawing\n",
                            tmpFactor < 0 ? "back" : "front");
        viewOrientationFactor = tmpFactor;
        draw();

        QString cmdStr = QStringLiteral("ActiveSketch.ViewObject.TempoVis.sketchClipPlane("
                                        "ActiveSketch, ActiveSketch.ViewObject.SectionView, %1)\n")
                             .arg(tmpFactor < 0 ? QLatin1String("True") : QLatin1String("False"));
        Base::Interpreter().runStringObject(cmdStr.toLatin1());
    }

    drawGrid(true);
}

int ViewProviderSketch::getPreselectPoint() const
{
    if (isInEditMode())
        return preselection.PreselectPoint;
    return -1;
}

int ViewProviderSketch::getPreselectCurve() const
{
    if (isInEditMode())
        return preselection.PreselectCurve;
    return -1;
}

int ViewProviderSketch::getPreselectCross() const
{
    // TODO: This function spreads over several files. It should be refactored into something less
    // "numeric" at a second stage.
    if (isInEditMode())
        return static_cast<int>(preselection.PreselectCross);
    return -1;
}

Sketcher::SketchObject* ViewProviderSketch::getSketchObject() const
{
    return dynamic_cast<Sketcher::SketchObject*>(pcObject);
}

const Sketcher::Sketch& ViewProviderSketch::getSolvedSketch() const
{
    return const_cast<const Sketcher::SketchObject*>(getSketchObject())->getSolvedSketch();
}

void ViewProviderSketch::deleteSelected()
{
    std::vector<Gui::SelectionObject> selection;
    selection = Gui::Selection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId());

    // only one sketch with its subelements are allowed to be selected
    if (selection.size() != 1) {
        Base::Console().DeveloperWarning(
            "ViewProviderSketch",
            "Delete: Selection not restricted to one sketch and its subelements\n");
        return;
    }

    // get the needed lists and objects
    const std::vector<std::string>& SubNames = selection[0].getSubNames();

    if (!SubNames.empty()) {
        App::Document* doc = getSketchObject()->getDocument();

        doc->openTransaction("Delete sketch geometry");

        onDelete(SubNames);

        doc->commitTransaction();
    }
}

bool ViewProviderSketch::onDelete(const std::vector<std::string>& subList)
{
    if (isInEditMode()) {
        std::vector<std::string> SubNames = subList;

        Gui::Selection().clearSelection();
        resetPreselectPoint();

        std::set<int> delInternalGeometries, delExternalGeometries, delCoincidents, delConstraints;
        // go through the selected subelements
        for (std::vector<std::string>::const_iterator it = SubNames.begin(); it != SubNames.end();
             ++it) {
            if (it->size() > 4 && it->substr(0, 4) == "Edge") {
                int GeoId = std::atoi(it->substr(4, 4000).c_str()) - 1;
                if (GeoId >= 0) {
                    delInternalGeometries.insert(GeoId);
                }
                else
                    delExternalGeometries.insert(Sketcher::GeoEnum::RefExt - GeoId);
            }
            else if (it->size() > 12 && it->substr(0, 12) == "ExternalEdge") {
                int GeoId = std::atoi(it->substr(12, 4000).c_str()) - 1;
                delExternalGeometries.insert(GeoId);
            }
            else if (it->size() > 6 && it->substr(0, 6) == "Vertex") {
                int VtId = std::atoi(it->substr(6, 4000).c_str()) - 1;
                int GeoId;
                Sketcher::PointPos PosId;
                getSketchObject()->getGeoVertexIndex(VtId, GeoId, PosId);
                if (getSketchObject()->getGeometry(GeoId)->is<Part::GeomPoint>()) {
                    if (GeoId >= 0)
                        delInternalGeometries.insert(GeoId);
                    else
                        delExternalGeometries.insert(Sketcher::GeoEnum::RefExt - GeoId);
                }
                else
                    delCoincidents.insert(VtId);
            }
            else if (*it == "RootPoint") {
                delCoincidents.insert(Sketcher::GeoEnum::RtPnt);
            }
            else if (it->size() > 10 && it->substr(0, 10) == "Constraint") {
                int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(*it);
                delConstraints.insert(ConstrId);
            }
        }

        // We stored the vertices, but is there really a coincident constraint? Check
        const std::vector<Sketcher::Constraint*>& vals = getSketchObject()->Constraints.getValues();

        std::set<int>::const_reverse_iterator rit;

        for (rit = delConstraints.rbegin(); rit != delConstraints.rend(); ++rit) {
            try {
                Gui::cmdAppObjectArgs(getObject(), "delConstraint(%d)", *rit);
            }
            catch (const Base::Exception& e) {
                Base::Console().DeveloperError("ViewProviderSketch", "%s\n", e.what());
            }
        }

        for (rit = delCoincidents.rbegin(); rit != delCoincidents.rend(); ++rit) {
            int GeoId;
            PointPos PosId;

            if (*rit == GeoEnum::RtPnt) {// RootPoint
                GeoId = Sketcher::GeoEnum::RtPnt;
                PosId = Sketcher::PointPos::start;
            }
            else {
                getSketchObject()->getGeoVertexIndex(*rit, GeoId, PosId);
            }

            if (GeoId != GeoEnum::GeoUndef) {
                for (std::vector<Sketcher::Constraint*>::const_iterator it = vals.begin();
                     it != vals.end();
                     ++it) {
                    if (((*it)->Type == Sketcher::Coincident)
                        && (((*it)->First == GeoId && (*it)->FirstPos == PosId)
                            || ((*it)->Second == GeoId && (*it)->SecondPos == PosId))) {
                        try {
                            Gui::cmdAppObjectArgs(
                                getObject(), "delConstraintOnPoint(%d,%d)", GeoId, (int)PosId);
                        }
                        catch (const Base::Exception& e) {
                            Base::Console().DeveloperError("ViewProviderSketch", "%s\n", e.what());
                        }
                        break;
                    }
                }
            }
        }

        if (!delInternalGeometries.empty()) {
            std::stringstream stream;

            // NOTE: SketchObject delGeometries will sort the array, so filling it here with a
            // reverse iterator would lead to the worst case scenario for sorting.
            auto endit = std::prev(delInternalGeometries.end());
            for (auto it = delInternalGeometries.begin(); it != endit; ++it) {
                stream << *it << ",";
            }

            stream << *endit;

            try {
                Gui::cmdAppObjectArgs(getObject(), "delGeometries([%s])", stream.str().c_str());
            }
            catch (const Base::Exception& e) {
                Base::Console().DeveloperError("ViewProviderSketch", "%s\n", e.what());
            }

            stream.str(std::string());
        }

        for (rit = delExternalGeometries.rbegin(); rit != delExternalGeometries.rend(); ++rit) {
            try {
                Gui::cmdAppObjectArgs(getObject(), "delExternal(%d)", *rit);
            }
            catch (const Base::Exception& e) {
                Base::Console().DeveloperError("ViewProviderSketch", "%s\n", e.what());
            }
        }

        int ret = getSketchObject()->solve();

        if (ret != 0) {
            // if the sketched could not be solved, we first redraw to update the UI geometry as
            // onChanged did not update it.
            UpdateSolverInformation();
            draw(false, true);

            signalConstraintsChanged();
            signalElementsChanged();
        }

        // Notes on solving and recomputing:
        //
        // This function is generally called from StdCmdDelete::activated
        // Since 2015-05-03 that function includes a recompute at the end.

        // Since December 2018, the function is no longer called from StdCmdDelete::activated,
        // as there is an event filter installed that intercepts the del key event. So now we do
        // need to tidy up after ourselves again.

        if (viewProviderParameters.autoRecompute) {
            Gui::Command::updateActive();
        }
        else {
            editCoinManager->drawConstraintIcons();
            this->updateColor();
        }

        // if in edit not delete the object
        return false;
    }
    // if not in edit delete the whole object
    return PartGui::ViewProviderPart::onDelete(subList);
}

QIcon ViewProviderSketch::mergeColorfulOverlayIcons(const QIcon& orig) const
{
    QIcon mergedicon = orig;

    if (!getSketchObject()->FullyConstrained.getValue()) {
        QPixmap px;

        static const char* const sketcher_notfullyconstrained_xpm[] = {"9 9 3 1",
                                                                       ". c None",
                                                                       "# c #dbaf00",
                                                                       "a c #ffcc00",
                                                                       "##.....##",
                                                                       "#a#...#a#",
                                                                       "#aa#.#aa#",
                                                                       ".#a#.#a#.",
                                                                       ".#a#.#a#.",
                                                                       ".#a#.#a#.",
                                                                       "#aa#.#aa#",
                                                                       "#a#...#a#",
                                                                       "##.....##"};
        px = QPixmap(sketcher_notfullyconstrained_xpm);

        mergedicon = Gui::BitmapFactoryInst::mergePixmap(
            mergedicon, px, Gui::BitmapFactoryInst::BottomRight);
    }

    return Gui::ViewProvider::mergeColorfulOverlayIcons(mergedicon);
}

void ViewProviderSketch::slotToolWidgetChanged(QWidget* newwidget)
{
    if (sketchHandler)
        sketchHandler->toolWidgetChanged(newwidget);
}

/*************************** functions ViewProviderSketch offers to friends such as
 * DrawHandlerSketch ************************/

void ViewProviderSketch::setConstraintSelectability(bool enabled /*= true*/)
{
    editCoinManager->setConstraintSelectability(enabled);
}

void ViewProviderSketch::setPositionText(const Base::Vector2d& Pos, const SbString& text)
{
    editCoinManager->setPositionText(Pos, text);
}

void ViewProviderSketch::setPositionText(const Base::Vector2d& Pos)
{
    editCoinManager->setPositionText(Pos);
}

void ViewProviderSketch::resetPositionText()
{
    editCoinManager->resetPositionText();
}

void ViewProviderSketch::setPreselectPoint(int PreselectPoint)
{
    preselection.PreselectPoint = PreselectPoint;
    preselection.PreselectCurve = Preselection::InvalidCurve;
    preselection.PreselectCross = Preselection::Axes::None;
    ;
    preselection.PreselectConstraintSet.clear();
}

void ViewProviderSketch::setPreselectRootPoint()
{
    preselection.PreselectPoint = Preselection::InvalidPoint;
    preselection.PreselectCurve = Preselection::InvalidCurve;
    preselection.PreselectCross = Preselection::Axes::RootPoint;
    preselection.PreselectConstraintSet.clear();
}


void ViewProviderSketch::resetPreselectPoint()
{
    preselection.PreselectPoint = Preselection::InvalidPoint;
    preselection.PreselectCurve = Preselection::InvalidCurve;
    preselection.PreselectCross = Preselection::Axes::None;
    ;
    preselection.PreselectConstraintSet.clear();
}

void ViewProviderSketch::addSelectPoint(int SelectPoint)
{
    selection.SelPointSet.insert(SelectPoint);
}

void ViewProviderSketch::removeSelectPoint(int SelectPoint)
{
    selection.SelPointSet.erase(SelectPoint);
}

void ViewProviderSketch::clearSelectPoints()
{
    selection.SelPointSet.clear();
}

bool ViewProviderSketch::isSelected(const std::string& subNameSuffix) const
{
    return Gui::Selection().isSelected(
        editDocName.c_str(), editObjName.c_str(), (editSubName + subNameSuffix).c_str());
}

void ViewProviderSketch::rmvSelection(const std::string& subNameSuffix)
{
    Gui::Selection().rmvSelection(
        editDocName.c_str(), editObjName.c_str(), (editSubName + subNameSuffix).c_str());
}

bool ViewProviderSketch::addSelection(const std::string& subNameSuffix, float x, float y, float z)
{
    return Gui::Selection().addSelection(
        editDocName.c_str(), editObjName.c_str(), (editSubName + subNameSuffix).c_str(), x, y, z);
}

bool ViewProviderSketch::addSelection2(const std::string& subNameSuffix, float x, float y, float z)
{
    return Gui::Selection().addSelection2(
        editDocName.c_str(), editObjName.c_str(), (editSubName + subNameSuffix).c_str(), x, y, z);
}

bool ViewProviderSketch::setPreselect(const std::string& subNameSuffix, float x, float y, float z)
{
    return Gui::Selection().setPreselect(
        editDocName.c_str(), editObjName.c_str(), (editSubName + subNameSuffix).c_str(), x, y, z);
}

/*************************** private functions to decouple Attorneys and Clients
 * ********************************************/

// Establishes a private collaboration interface with EditModeCoinManager to perform
// EditModeCoinManager tasks, while abstracting EditModeCoinManager from the specific
// ViewProviderSketch implementation, while allowing ViewProviderSketch to fully delegate coin
// management.

const std::vector<Sketcher::Constraint*> ViewProviderSketch::getConstraints() const
{
    return getSketchObject()->Constraints.getValues();
}

Sketcher::Constraint* ViewProviderSketch::getConstraint(int constid) const
{
    Sketcher::SketchObject* obj = getSketchObject();
    const std::vector<Sketcher::Constraint*>& constrlist = obj->Constraints.getValues();
    if (constid >= 0 || constid < int(constrlist.size())) {
        return constrlist[constid];
    }

    return nullptr;
}

const GeoList ViewProviderSketch::getGeoList() const
{
    const std::vector<Part::Geometry*> tempGeo =
        getSketchObject()->getCompleteGeometry();// without memory allocation

    int intGeoCount = getSketchObject()->getHighestCurveIndex() + 1;

    auto geolist = GeoList::getGeoListModel(std::move(tempGeo), intGeoCount);

    return geolist;
}

bool ViewProviderSketch::constraintHasExpression(int constrid) const
{
    return getSketchObject()->constraintHasExpression(constrid);
}

std::unique_ptr<SoRayPickAction> ViewProviderSketch::getRayPickAction() const
{
    assert(isInEditMode());
    Gui::MDIView* mdi =
        Gui::Application::Instance->editViewOfNode(editCoinManager->getRootEditNode());
    if (!(mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())))
        return nullptr;
    Gui::View3DInventorViewer* viewer = static_cast<Gui::View3DInventor*>(mdi)->getViewer();

    return std::make_unique<SoRayPickAction>(viewer->getSoRenderManager()->getViewportRegion());
}

SbVec2f ViewProviderSketch::getScreenCoordinates(SbVec2f sketchcoordinates) const
{

    Base::Placement sketchPlacement = getEditingPlacement();
    Base::Vector3d sketchPos(sketchPlacement.getPosition());
    Base::Rotation sketchRot(sketchPlacement.getRotation());

    // get global coordinates from sketcher coordinates
    Base::Vector3d pos(sketchcoordinates[0], sketchcoordinates[1], 0);
    sketchRot.multVec(pos, pos);
    pos = pos + sketchPos;

    Gui::MDIView* mdi = this->getActiveView();
    Gui::View3DInventor* view = qobject_cast<Gui::View3DInventor*>(mdi);
    if (!view || !isInEditMode())
        return SbVec2f(0, 0);

    Gui::View3DInventorViewer* viewer = view->getViewer();

    SoCamera* pCam = viewer->getSoRenderManager()->getCamera();

    if (!pCam)
        return SbVec2f(0, 0);

    SbViewVolume vol = pCam->getViewVolume();
    Gui::ViewVolumeProjection proj(vol);

    // dimensionless [0 1] (or 1.5 see View3DInventorViewer.cpp )
    Base::Vector3d screencoords = proj(pos);

    int width = viewer->getGLWidget()->width(), height = viewer->getGLWidget()->height();

    if (width >= height) {
        // "Landscape" orientation, to square
        screencoords.x *= height;
        screencoords.x += (width - height) / 2.0;
        screencoords.y *= height;
    }
    else {
        // "Portrait" orientation
        screencoords.x *= width;
        screencoords.y *= width;
        screencoords.y += (height - width) / 2.0;
    }

    SbVec2f iconCoords(screencoords.x, screencoords.y);

    return iconCoords;
}

QFont ViewProviderSketch::getApplicationFont() const
{
    return QApplication::font();
}

int ViewProviderSketch::defaultFontSizePixels() const
{
    QFontMetricsF metrics(QApplication::font());
    return static_cast<int>(metrics.height());
}

int ViewProviderSketch::getApplicationLogicalDPIX() const
{
    return int(QApplication::primaryScreen()->logicalDotsPerInchX());
}

int ViewProviderSketch::getViewOrientationFactor() const
{
    return viewOrientationFactor;
}

double ViewProviderSketch::getRotation(SbVec3f pos0, SbVec3f pos1) const
{
    double x0, y0, x1, y1;

    Gui::MDIView* mdi =
        Gui::Application::Instance->editViewOfNode(editCoinManager->getRootEditNode());
    if (!(mdi && mdi->isDerivedFrom(Gui::View3DInventor::getClassTypeId())))
        return 0;
    Gui::View3DInventorViewer* viewer = static_cast<Gui::View3DInventor*>(mdi)->getViewer();
    SoCamera* pCam = viewer->getSoRenderManager()->getCamera();
    if (!pCam)
        return 0;

    try {
        SbViewVolume vol = pCam->getViewVolume();

        getCoordsOnSketchPlane(pos0, vol.getProjectionDirection(), x0, y0);
        getCoordsOnSketchPlane(pos1, vol.getProjectionDirection(), x1, y1);

        return -atan2((y1 - y0), (x1 - x0)) * 180 / M_PI;
    }
    catch (const Base::ZeroDivisionError&) {
        return 0;
    }
}


GeoListFacade ViewProviderSketch::getGeoListFacade() const
{
    return getSketchObject()->getGeoListFacade();
}

bool ViewProviderSketch::isSketchInvalid() const
{

    bool sketchinvalid = getSketchObject()->getLastHasRedundancies()
        || getSketchObject()->getLastHasConflicts()
        || getSketchObject()->getLastHasMalformedConstraints();
    return sketchinvalid;
}

bool ViewProviderSketch::isSketchFullyConstrained() const
{
    return getSketchObject()->FullyConstrained.getValue();
}

bool ViewProviderSketch::haveConstraintsInvalidGeometry() const
{
    return getSketchObject()->Constraints.hasInvalidGeometry();
}

void ViewProviderSketch::addNodeToRoot(SoSeparator* node)
{
    pcRoot->addChild(node);
}

void ViewProviderSketch::removeNodeFromRoot(SoSeparator* node)
{
    pcRoot->removeChild(node);
}

bool ViewProviderSketch::isConstraintPreselected(int constraintId) const
{
    return preselection.PreselectConstraintSet.count(constraintId);
}

bool ViewProviderSketch::isPointSelected(int pointId) const
{
    return selection.SelPointSet.find(pointId) != selection.SelPointSet.end();
}

bool ViewProviderSketch::isCurveSelected(int curveId) const
{
    return selection.SelCurvSet.find(curveId) != selection.SelCurvSet.end();
}

bool ViewProviderSketch::isConstraintSelected(int constraintId) const
{
    return selection.SelConstraintSet.find(constraintId) != selection.SelConstraintSet.end();
}

void ViewProviderSketch::executeOnSelectionPointSet(
    std::function<void(const int)>&& operation) const
{
    for (const auto v : selection.SelPointSet)
        operation(v);
}

bool ViewProviderSketch::isInEditMode() const
{
    return editCoinManager != nullptr;
}
void ViewProviderSketch::generateContextMenu()
{
    int selectedEdges = 0;
    int selectedLines = 0;
    int selectedConics = 0;
    int selectedPoints = 0;
    int selectedConstraints = 0;
    int selectedBsplines = 0;
    int selectedBsplineKnots = 0;
    int selectedOrigin = 0;
    int selectedEndPoints = 0;
    bool onlyOrigin = false;

    Gui::MenuItem menu;
    menu.setCommand("Sketcher context");

    std::vector<Gui::SelectionObject> selection =
        Gui::Selection().getSelectionEx(0, Sketcher::SketchObject::getClassTypeId());

    // if something is selected, count different elements in the current selection
    if (selection.size() > 0) {
        const std::vector<std::string> SubNames = selection[0].getSubNames();
        const Sketcher::SketchObject* obj;
        if (selection[0].getObject()->isDerivedFrom<Sketcher::SketchObject>()) {
            obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
            for (auto& name : SubNames) {
                int geoId = std::atoi(name.substr(4, 4000).c_str()) - 1;
                const Part::Geometry* geo = getSketchObject()->getGeometry(geoId);
                if (name.substr(0, 4) == "Edge" || name.substr(0, 12) == "ExternalEdge") {
                    ++selectedEdges;

                    if (geoId >= 0) {
                        if (isLineSegment(*geo)) {
                            ++selectedLines;
                        }
                        else if (geo->is<Part::GeomBSplineCurve>()) {
                            ++selectedBsplines;
                        }
                        else {
                            ++selectedConics;
                        }
                    }
                }
                else if (name.substr(0, 4) == "Vert") {
                    ++selectedPoints;
                    Sketcher::PointPos posId;
                    getIdsFromName(name, obj, geoId, posId);
                    if (isBsplineKnotOrEndPoint(obj, geoId, posId)) {
                        ++selectedBsplineKnots;
                    }

                    ++selectedEndPoints;
                }
                else if (name.substr(0, 4) == "Cons") {
                    ++selectedConstraints;
                }
                else if (name.substr(2, 5) == "Axis") {
                    ++selectedEdges;
                    ++selectedLines;
                    ++selectedOrigin;
                }
                else if (name.substr(0, 4) == "Root") {
                    ++selectedPoints;
                    ++selectedOrigin;
                }
            }
        }
        if (selectedPoints + selectedEdges == selectedOrigin) {
            onlyOrigin = true;
        }
        // build context menu items depending on the selection
        if (selectedBsplines > 0 && selectedBsplines == selectedEdges && selectedPoints == 0
            && !onlyOrigin) {
            menu << "Sketcher_BSplineInsertKnot"
                 << "Sketcher_BSplineIncreaseDegree"
                 << "Sketcher_BSplineDecreaseDegree";
        }
        else if (selectedBsplineKnots > 0 && selectedBsplineKnots == selectedPoints
                 && selectedEdges == 0 && !onlyOrigin) {
            if (selectedBsplineKnots == 1) {
                menu << "Sketcher_BSplineIncreaseKnotMultiplicity"
                     << "Sketcher_BSplineDecreaseKnotMultiplicity";
            }
        }
        if (selectedEdges >= 1 && selectedPoints == 0 && selectedBsplines == 0 && !onlyOrigin) {
            menu << "Sketcher_Dimension";
            if (selectedConics == 0) {
                menu << "Sketcher_ConstrainHorVer"
                     << "Sketcher_ConstrainHorizontal"
                     << "Sketcher_ConstrainVertical";

                if (selectedLines > 1) {
                    menu << "Sketcher_ConstrainParallel";

                    if (selectedLines == 2) {
                        menu << "Sketcher_ConstrainPerpendicular"
                             << "Sketcher_ConstrainTangent";
                    }

                    menu << "Sketcher_ConstrainEqual";
                }
                menu << "Sketcher_ConstrainBlock";
            }
            else if (selectedConics > 1 && selectedLines == 0) {
                menu << "Sketcher_ConstrainCoincidentUnified"
                     << "Sketcher_ConstrainTangent"
                     << "Sketcher_ConstrainEqual";
            }
            else if (selectedConics == 1 && selectedLines == 1) {
                menu << "Sketcher_ConstrainPerpendicular"
                     << "Sketcher_ConstrainTangent";
            }
        }
        else if (selectedEdges == 1 && selectedPoints >= 1 && !onlyOrigin) {
            menu << "Sketcher_Dimension";
            if (selectedConics == 0 && selectedBsplines == 0) {
                menu << "Sketcher_ConstrainCoincidentUnified"
                     << "Sketcher_ConstrainHorVer"
                     << "Sketcher_ConstrainHorizontal"
                     << "Sketcher_ConstrainVertical";
                if (selectedPoints == 2) {
                    menu << "Sketcher_ConstrainSymmetric";
                }
                if (selectedPoints == 1) {
                    menu << "Sketcher_ConstrainPerpendicular"
                         << "Sketcher_ConstrainTangent";
                }
            }
            else {
                menu << "Sketcher_ConstrainCoincidentUnified"
                     << "Sketcher_ConstrainPerpendicular"
                     << "Sketcher_ConstrainTangent";
            }
        }
        else if (selectedEdges == 0 && selectedPoints >= 1 && !onlyOrigin) {
            menu << "Sketcher_Dimension";

            if (selectedPoints > 1) {
                menu << "Sketcher_ConstrainCoincidentUnified"
                     << "Sketcher_ConstrainHorVer"
                     << "Sketcher_ConstrainHorizontal"
                     << "Sketcher_ConstrainVertical";
            }
            if (selectedPoints == 2) {
                menu << "Sketcher_ConstrainPerpendicular"
                     << "Sketcher_ConstrainTangent";
                if (selectedEndPoints == 2) {
                    menu << "Sketcher_JoinCurves";
                }
            }
        }
        else if (selectedLines >= 1 && selectedPoints >= 1 && !onlyOrigin) {
            menu << "Sketcher_Dimension"
                 << "Sketcher_ConstrainHorVer"
                 << "Sketcher_ConstrainHorizontal"
                 << "Sketcher_ConstrainVertical";
        }
        // context menu if only constraints are selected
        else if (selectedConstraints >= 1) {
            menu << "Sketcher_ToggleDrivingConstraint"
                 << "Sketcher_ToggleActiveConstraint"
                 << "Sketcher_SelectElementsAssociatedWithConstraints"
                 << "Separator"
                 << "Std_Delete";
        }
        // add the rest of the context menu if geometry is selected
        if (selectedPoints != 0 || selectedEdges != 0) {
            menu << "Separator"
                 << "Sketcher_ToggleConstruction"
                 << "Separator"
                 << "Sketcher_Translate"
                 << "Sketcher_Rotate"
                 << "Sketcher_Scale"
                 << "Sketcher_Offset"
                 << "Separator"
                 << "Sketcher_CompDimensionTools"
                 << "Sketcher_CompConstrainTools"
                 << "Separator"
                 << "Sketcher_SelectConstraints"
                 << "Separator"
                 << "Sketcher_CopyClipboard"
                 << "Sketcher_Cut"
                 << "Sketcher_Paste"
                 << "Separator"
                 << "Std_Delete";
        }
    }
    // context menu without a selection
    else {
        menu << "Sketcher_ViewSketch"
             << "Sketcher_ViewSection"
             << "Std_ViewFitAll"
             << "Separator"
             << "Sketcher_CreatePoint"
             << "Sketcher_CreatePolyline"
             << "Sketcher_CreateArc"
             << "Sketcher_CreateCircle"
             << "Sketcher_CreateRectangle"
             << "Sketcher_CreateHexagon"
             << "Sketcher_CreateBSpline"
             << "Separator"
             << "Sketcher_ToggleConstruction"
             << "Separator"
             << "Sketcher_CreateFillet"
             << "Sketcher_CreateChamfer"
             << "Sketcher_Trimming"
             << "Sketcher_Extend"
             << "Separator"
             << "Sketcher_External"
             << "Separator"
             << "Sketcher_CompDimensionTools"
             << "Sketcher_CompConstrainTools"
             << "Separator"
             << "Sketcher_DeleteAllGeometry"
             << "Sketcher_DeleteAllConstraints"
             << "Separator"
             << "Sketcher_Paste"
             << "Separator"
             << "Sketcher_LeaveSketch";
    }
    // create context menu
    Gui::Application::Instance->setupContextMenu("Sketch", &menu);
    QMenu contextMenu(
        qobject_cast<Gui::View3DInventor*>(this->getActiveView())->getViewer()->getGLWidget());
    Gui::MenuManager::getInstance()->setupContextMenu(&menu, contextMenu);
    contextMenu.exec(QCursor::pos());
}

void ViewProviderSketch::preselectToSelection(const std::stringstream& ss,
                                              boost::scoped_ptr<SoPickedPoint>& pp,
                                              bool toggle)
{
    // If toggle true and preselection already selected remove from selection
    if (toggle && isSelected(ss.str())) {
        rmvSelection(ss.str());
    }
    // add to selection
    else {
        addSelection2(ss.str(), pp->getPoint()[0], pp->getPoint()[1], pp->getPoint()[2]);
        drag.resetIds();
    }
}
// clang-format on
